Merge TQ2A.230405.003 am: 5642b7f072 am: f63569cd3a am: 2e6c9aaf12

Original change: https://android-review.googlesource.com/c/platform/packages/services/Telecomm/+/2528314

Change-Id: Iabb211305d17e84397064a80854568dacb15ecf4
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/Android.bp b/Android.bp
index 1b422aa..c5141ca 100644
--- a/Android.bp
+++ b/Android.bp
@@ -28,6 +28,9 @@
     static_libs: [
         "androidx.annotation_annotation",
     ],
+    libs: [
+        "services",
+    ],
     resource_dirs: ["res"],
     proto: {
         type: "nano",
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index c26650c..d42dcff 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -64,6 +64,7 @@
     <uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
     <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
     <uses-permission android:name="com.android.phone.permission.ACCESS_LAST_KNOWN_CELL_ID"/>
+    <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
 
     <permission android:name="android.permission.BROADCAST_CALLLOG_INFO"
          android:label="Broadcast the call type/duration information"
@@ -137,6 +138,7 @@
              android:permission="android.permission.CALL_PHONE"
              android:excludeFromRecents="true"
              android:process=":ui"
+             android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|density|fontScale|keyboard|layoutDirection|locale|navigation|smallestScreenSize|touchscreen|uiMode"
              android:exported="true">
             <!-- CALL action intent filters for the various ways of initiating an outgoing call. -->
             <intent-filter>
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 9874044..489fab7 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,2 +1,3 @@
-[Hook Scripts]
-aosp_hook = ${REPO_ROOT}/packages/services/Telecomm/scripts/aosp_tag_preupload.py ${PREUPLOAD_COMMIT}
+# Uncomment to re-enable aosp warning.
+#[Hook Scripts]
+#aosp_hook = ${REPO_ROOT}/packages/services/Telecomm/scripts/aosp_tag_preupload.py ${PREUPLOAD_COMMIT}
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 2707a08..61381ae 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Die oproep na <xliff:g id="CALLER">%s</xliff:g> is ontkoppel as gevolg van \'n noodoproep wat gemaak word."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Jou oproep is ontkoppel as gevolg van \'n noodoproep wat gemaak word."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Agtergrondoproep"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> het \'n oproep in die agtergrond geplaas. Hierdie program kan dalk toegang tot oudio kry en dit oor die oproep speel."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> verwerk ’n oproep in die agtergrond. Hierdie program kan dalk toegang tot oudio kry en dit oor die oproep speel."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> het opgehou reageer"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Jou oproep het die foonprogram gebruik wat saam met jou toestel gekom het"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Oproep stilgemaak."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Telecom-ontwikkelaarkieslys"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Oproepe kan nie gedurende \'n noodoproep geneem word nie."</string>
     <string name="cancel" msgid="6733466216239934756">"Kanselleer"</string>
+    <string name="back" msgid="6915955601805550206">"Terug"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Oorstuk"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Kabelkopstuk"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Luidspreker"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Ekstern"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Onbekend"</string>
 </resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index b171af6..fc36464 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"የአደጋ ጊዜ ጥሪ እየተደረገ ስለሆነ ወደ <xliff:g id="CALLER">%s</xliff:g> የሚደረገው ጥሪ ተቋርጧል።"</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"የአደጋ ጊዜ ጥሪ እየተደረገ ስለሆነ የእርስዎ ጥሪ ተቋርጧል።"</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"የጀርባ ጥሪ"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> ጥሪን ወደ ጀርባ አስቀምጧል። ይህ መተግበሪያ ጥሪው ላይ ኦዲዮ ላይ እየደረሰ ወይም እያጫወተ ሊሆን ይችላል።"</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> ከዳራ ጥሪ እያስሄደ ነው። ይህ መተግበሪያ ወደ ጥሪው ኦዲዮ እየደረሰ ወይም እያጫወተ ሊሆን ይችላል።"</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> ምላሽ መስጠት አቁሟል"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"ጥሪዎ ከእርስዎ መሣሪያ ጋር የመጣውን የስልክ መተግበሪያ ተጠቅሟል"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"ጥሪ ፀጥ  ብሏል"</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"የቴሌኮም ገንቢ ምናሌ"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"ጥሪዎች በአደጋ ጊዜ ጥሪ ላይ ሊነሱ አይችሉም።"</string>
     <string name="cancel" msgid="6733466216239934756">"ይቅር"</string>
+    <string name="back" msgid="6915955601805550206">"ተመለስ"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"ማዳመጫ"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"ብሉቱዝ"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"ባለገመድ ማዳመጫ"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"ድምጽ ማውጫ"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"ውጫዊ"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"ያልታወቀ"</string>
 </resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index d310271..b9f8842 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"تم قطع اتصالك بجهة الاتصال <xliff:g id="CALLER">%s</xliff:g> بسبب إجراء مكالمة طوارئ."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"تم قطع مكالمتك بسبب إجراء مكالمة طوارئ."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"مكالمة في الخلفية"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"وضَع <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> مكالمة في الخلفية. يمكن لهذا التطبيق الوصول إلى الصوت وتشغيله عبر المكالمة."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"يعالج تطبيق <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> مكالمة في الخلفية. يمكن لهذا التطبيق الوصول إلى الصوت وتشغيله عبر المكالمة."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"توقّف <xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> عن الاستجابة"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"تمت مكالمتك باستخدام تطبيق \"الهاتف\" الذي أتى مع جهازك."</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"تم كتم صوت المكالمة."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"قائمة مطوّر برامج الاتصالات"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"لا يمكن تلقّي المكالمات أثناء إجراء مكالمة طوارئ."</string>
     <string name="cancel" msgid="6733466216239934756">"إلغاء"</string>
+    <string name="back" msgid="6915955601805550206">"رجوع"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"سماعة الأذن"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"البلوتوث"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"سماعة رأس سلكية"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"مكبّر صوت"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"المصادر الخارجية"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"غير معروف"</string>
 </resources>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index 33a56ad..9226599 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -30,15 +30,15 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"এট জৰুৰীকালীন কল কৰাৰ কাৰণে <xliff:g id="CALLER">%s</xliff:g>লৈ কৰা কলটোৰ সংযোগ বিচ্ছিন্ন কৰা হৈছে।"</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"এট জৰুৰীকালীন কল কৰাৰ কাৰণে আপোনাৰ কলটোৰ সংযোগ বিচ্ছিন্ন কৰা হৈছে।"</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"নেপথ্যৰ কল"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g>এ নেপথ্যত এটা কল কৰিছে। এই এপ্‌টোৱে কলটোত অডিঅ’ এক্সেছ আৰু প্লে’ কৰি থাকিব পাৰে।"</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g>এ নেপথ্যত এটা কল প্ৰক্ৰিয়াকৰণ কৰি আছে। এই এপ্‌টোৱে কলটো চলি থাকোঁতে অডিঅ’ এক্সেছ আৰু প্লে’ কৰি থাকিব পাৰে।"</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g>এ সঁহাৰি দিয়া বন্ধ কৰিছে"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"আপোনাৰ কলটোৱে আপোনাৰ ডিভাইচটোৰ লগত অহা ফ’ন এপ্‌টো ব্যৱহাৰ কৰিছে"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"কল মিউট কৰা হৈছে।"</string>
     <string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"স্পীকাৰফ\'ন সক্ষম কৰা হৈছে।"</string>
     <string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"এতিয়া কথা পাতিব নোৱাৰোঁ। কি খবৰ?"</string>
     <string name="respond_via_sms_canned_response_2" msgid="2052951316129952406">"মই আপোনাক লগে লগে কলবেক কৰি আছোঁ।"</string>
-    <string name="respond_via_sms_canned_response_3" msgid="6656147963478092035">"মই আপোনাক পিছত কল কৰিম।"</string>
-    <string name="respond_via_sms_canned_response_4" msgid="9141132488345561047">"এতিয়া কথা পাতিব নোৱাৰোঁ। মোক পিছত কল কৰিবনে?"</string>
+    <string name="respond_via_sms_canned_response_3" msgid="6656147963478092035">"মই আপোনাক পাছত কল কৰিম।"</string>
+    <string name="respond_via_sms_canned_response_4" msgid="9141132488345561047">"এতিয়া কথা পাতিব নোৱাৰোঁ। মোক পাছত কল কৰিবনে?"</string>
     <string name="respond_via_sms_setting_title" msgid="4762275482898830160">"ক্ষীপ্ৰ উত্তৰসমূহ"</string>
     <string name="respond_via_sms_setting_title_2" msgid="4914853536609553457">"ক্ষীপ্ৰ উত্তৰসমূহ সম্পাদনা কৰক"</string>
     <string name="respond_via_sms_setting_summary" msgid="8054571501085436868"></string>
@@ -57,9 +57,9 @@
     <string name="change_default_dialer_dialog_affirmative" msgid="8604665314757739550">"ডিফ\'ল্ট ছেট কৰক"</string>
     <string name="change_default_dialer_dialog_negative" msgid="8648669840052697821">"বাতিল কৰক"</string>
     <string name="change_default_dialer_warning_message" msgid="8461963987376916114">"<xliff:g id="NEW_APP">%s</xliff:g>এ কল কৰা লগতে কলৰ সকলো দিশ নিয়ন্ত্ৰণ কৰিবলৈ সক্ষম হ\'ব। কেৱল আপুনি সম্পূৰ্ণৰূপে বিশ্বাস কৰা এপক হে আপোনাৰ ডিফ\'ল্ট ফ\'ন এপ্ হিচাপে চিহ্নিত কৰা উচিত।"</string>
-    <string name="change_default_call_screening_dialog_title" msgid="5365787219927262408">"<xliff:g id="NEW_APP">%s</xliff:g>ক আপোনাৰ ডিফ\'ল্ট কল স্ক্ৰীণ কৰা এপ্ হিচাপে ছেট কৰিবনে?"</string>
-    <string name="change_default_call_screening_warning_message_for_disable_old_app" msgid="2039830033533243164">"<xliff:g id="OLD_APP">%s</xliff:g>য়ে আৰু কল স্ক্ৰীণ কৰিব নোৱাৰিব।"</string>
-    <string name="change_default_call_screening_warning_message" msgid="9020537562292754269">"<xliff:g id="NEW_APP">%s</xliff:g>য়ে আপোনাৰ সম্পৰ্কসূচীত নথকা কল কৰোঁতাৰ বিষয়ে তথ্য চাব আৰু এই কলবোৰ অৱৰোধ কৰিব পাৰিব। আপুনি বিশ্বাস কৰা এপবোৰহে ডিফ\'ল্ট কল স্ক্ৰীণ কৰা এপ্ হিচাপে ছেট কৰা উচিত।"</string>
+    <string name="change_default_call_screening_dialog_title" msgid="5365787219927262408">"<xliff:g id="NEW_APP">%s</xliff:g>ক আপোনাৰ ডিফ\'ল্ট কল স্ক্ৰীন কৰা এপ্ হিচাপে ছেট কৰিবনে?"</string>
+    <string name="change_default_call_screening_warning_message_for_disable_old_app" msgid="2039830033533243164">"<xliff:g id="OLD_APP">%s</xliff:g>য়ে আৰু কল স্ক্ৰীন কৰিব নোৱাৰিব।"</string>
+    <string name="change_default_call_screening_warning_message" msgid="9020537562292754269">"<xliff:g id="NEW_APP">%s</xliff:g>য়ে আপোনাৰ সম্পৰ্কসূচীত নথকা কল কৰোঁতাৰ বিষয়ে তথ্য চাব আৰু এই কলবোৰ অৱৰোধ কৰিব পাৰিব। আপুনি বিশ্বাস কৰা এপবোৰহে ডিফ\'ল্ট কল স্ক্ৰীন কৰা এপ্ হিচাপে ছেট কৰা উচিত।"</string>
     <string name="change_default_call_screening_dialog_affirmative" msgid="7162433828280058647">"ডিফ\'ল্ট ছেট কৰক"</string>
     <string name="change_default_call_screening_dialog_negative" msgid="1839266125623106342">"বাতিল কৰক"</string>
     <string name="blocked_numbers" msgid="8322134197039865180">"অৱৰোধ কৰা নম্বৰসমূহ"</string>
@@ -73,7 +73,7 @@
     <string name="non_primary_user" msgid="315564589279622098">"কেৱল ডিভাইচটোৰ গৰাকীয়েহে অৱৰোধ কৰা নম্বৰসমূহ চাব আৰু পৰিচালনা কৰিব পাৰে।"</string>
     <string name="delete_icon_description" msgid="5335959254954774373">"অৱৰোধৰ পৰা আঁতৰাওক"</string>
     <string name="blocked_numbers_butter_bar_title" msgid="582982373755950791">"সাময়িকভাৱে অৱৰোধৰ সুবিধা বন্ধ কৰি থোৱা হৈছে"</string>
-    <string name="blocked_numbers_butter_bar_body" msgid="1261213114919301485">"আপুনি জৰুৰীকালীন নম্বৰ এটা ডায়েল কৰাৰ পিছত বা সেই নম্বৰটোলৈ পাঠ বাৰ্তা পঠিওৱাৰ পিছত নম্বৰটো অৱৰোধৰ পৰা আঁতৰোৱা হয় যাতে জৰুৰীকালীন সেৱাসমূহে আপোনাৰ সৈতে যোগাযোগ কৰিব পাৰে।"</string>
+    <string name="blocked_numbers_butter_bar_body" msgid="1261213114919301485">"আপুনি জৰুৰীকালীন নম্বৰ এটা ডায়েল কৰাৰ পাছত বা সেই নম্বৰটোলৈ পাঠ বাৰ্তা পঠিওৱাৰ পাছত নম্বৰটো অৱৰোধৰ পৰা আঁতৰোৱা হয় যাতে জৰুৰীকালীন সেৱাসমূহে আপোনাৰ সৈতে যোগাযোগ কৰিব পাৰে।"</string>
     <string name="blocked_numbers_butter_bar_button" msgid="2704456308072489793">"এতিয়াই পুনঃসক্ষম কৰক"</string>
     <string name="blocked_numbers_number_blocked_message" msgid="4314736791180919167">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> অৱৰোধ কৰা হৈছে"</string>
     <string name="blocked_numbers_number_unblocked_message" msgid="2933071624674945601">"<xliff:g id="UNBLOCKED_NUMBER">%1$s</xliff:g> অৱৰোধৰ পৰা আঁতৰ কৰা হৈছে"</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"দূৰ-সংযোগ সম্পৰ্কীয় বিকাশকৰ্তাৰ মেনু"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"কোনো জৰুৰীকালীন কলত থাকিলে কলসমূহ গ্ৰহণ কৰিব নোৱাৰি।"</string>
     <string name="cancel" msgid="6733466216239934756">"বাতিল কৰক"</string>
+    <string name="back" msgid="6915955601805550206">"উভতি যাওক"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"ইয়েৰপিচ"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"ব্লুটুথ"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"তাঁৰযুক্ত হেডছেট"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"স্পীকাৰ"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"বাহ্যিক"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"অজ্ঞাত"</string>
 </resources>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index 61dbd67..d2368fa 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Hazırda təcili zəng edildiyi üçün <xliff:g id="CALLER">%s</xliff:g> ilə zəng kəsilib."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Təcili zəng edildiyinə görə zənginizin əlaqəsi kəsildi."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Arxa fon zəngi"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> arxa fonda zəng edib. Bu tətbiq zəng ilə daxil ola və oxuda bilər."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> arxa fonda zəngi qəbul edir. Bu tətbiq zəng zamanı audioya daxil ola və oxuda bilər."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> tətbiqində xəta baş verdi"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Zəng üçün cihazda əvvəlcədən quraşdırılan telefon tətbiqindən istifadə edildi"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Səssiz zəng edin."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Telecom Tərtibatçı Menyusu"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Təcili zəng zamanı zəng edilə bilməz."</string>
     <string name="cancel" msgid="6733466216239934756">"Ləğv edin"</string>
+    <string name="back" msgid="6915955601805550206">"Geri"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Qulaqlıq"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Simli qulaqlıq"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Dinamik"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Xarici"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Naməlum"</string>
 </resources>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index 85dc005..f77b0bb 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Poziv sa <xliff:g id="CALLER">%s</xliff:g> je prekinut jer se upućuje hitni poziv."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Poziv je prekinut jer se upućuje hitni poziv."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Poziv u pozadini"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"Aplikacija <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> je uputila poziv u pozadini. Ona može da pristupa zvuku i pušta ga tokom poziva."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"Aplikacija <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> obrađuje poziv u pozadini. Ona može da pristupa zvuku i da ga pušta tokom poziva."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> više ne reaguje"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Poziv je koristio aplikaciju za telefoniranje koju ste dobili uz uređaj"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Zvuk poziva je isključen."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Meni za programere Telecom-a"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Za vreme hitnog poziva nije moguće preuzimati druge pozive."</string>
     <string name="cancel" msgid="6733466216239934756">"Otkaži"</string>
+    <string name="back" msgid="6915955601805550206">"Nazad"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Slušalica"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Žičane slušalice"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Zvučnik"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Eksterni"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Nepoznato"</string>
 </resources>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index 07e0adf..8560c9c 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Выклік да абанента <xliff:g id="CALLER">%s</xliff:g> перарваны, бо выконваецца экстранны выклік."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Бягучы выклік перарваны, бо выконваецца экстранны выклік."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Фонавы выклік"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"Праграма \"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g>\" перавяла выклік у фонавы рэжым і можа прайграваць аўдыя падчас выкліку."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"Праграма \"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g>\" апрацоўвае выклік у фонавым рэжыме. Яна можа прайграваць аўдыя падчас выкліку."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"Праграма \"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g>\" не адказвае"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Выклік зроблены ў стандартнай праграме \"Тэлефон\" на прыладзе"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Гук выключаны."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Меню распрацоўшчыка Telecom"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Падчас экстраннага выкліку іншыя выклікі прымаць немагчыма."</string>
     <string name="cancel" msgid="6733466216239934756">"Скасаваць"</string>
+    <string name="back" msgid="6915955601805550206">"Назад"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Дынамік"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Правадная гарнітура"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Знешні дынамік"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Знешняя прылада"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Невядома"</string>
 </resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 4bc13bb..c99dcd0 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Обаждането до <xliff:g id="CALLER">%s</xliff:g> бе прекъснато, тъй като се извършва спешно обаждане."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Обаждането ви бе прекъснато, тъй като се извършва спешно обаждане."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Обаждане: заден план"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> постави обаждане на заден план. Приложението може да има достъп до обаждането и да възпроизвежда звук върху него."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> обработва обаждане на заден план. Приложението може да има достъп до обаждането и да възпроизвежда звук върху него."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> престана да реагира"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Обаждането ви бе извършено с приложението за телефон, което сте получили с устройството си"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Обаждането бе спряно."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Меню за програмисти на Telecom"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"По време на спешно обаждане не могат да се поемат обаждания."</string>
     <string name="cancel" msgid="6733466216239934756">"Отказ"</string>
+    <string name="back" msgid="6915955601805550206">"Назад"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Слушалка"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Слушалки с кабел"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Високоговорител"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Външно"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Неизвестно"</string>
 </resources>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index 2ecd8e1..01b67f0 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"জরুরি কল করার জন্য <xliff:g id="CALLER">%s</xliff:g>-কে করা কল ডিসকানেক্ট করা হয়েছে।"</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"জরুরী কল করার জন্য আপনার কল ডিসকানেক্ট করা হয়েছে।"</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"ব্যাকগ্রাউন্ডের কল"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> ব্যাকগ্রাউন্ডে কল রেখেছে। এই অ্যাপটি হয়ত কলের মাধ্যমে অডিও অ্যাক্সেস করে চালাচ্ছে।"</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> ব্যাকগ্রাউন্ডে কল প্রসেস করছে। এই অ্যাপটি হয়ত কল থেকে অডিও অ্যাক্সেস করে চালাচ্ছে।"</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> কাজ করছে না"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"আপনার ডিভাইসের ফোন অ্যাপ ব্যবহার করে কল করা হত"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"কল মিউট করা আছে৷"</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"টেলিকম ডেভেলপার মেনু"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"জরুরি কল চলাকালীন কোনও কল রিসিভ করা যাবে না।"</string>
     <string name="cancel" msgid="6733466216239934756">"বাতিল করুন"</string>
+    <string name="back" msgid="6915955601805550206">"ফিরে যান"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"ইয়ারপিস"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"ব্লুটুথ"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"ওয়্যার্ড হেডসেট"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"স্পিকার"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"এক্সটার্নাল"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"অজানা"</string>
 </resources>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index a1f9156..201d8d1 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Poziv upućen kontaktu <xliff:g id="CALLER">%s</xliff:g> je prekinut zbog upućivanja hitnog poziva."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Poziv je prekinut zbog upućivanja hitnog poziva."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Poziv u pozadini"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"Aplikacija <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> je uputila poziv u pozadini. Ova aplikacija može pristupati zvuku i reproducirati ga tokom poziva."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"Aplikacija <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> obrađuje poziv u pozadini. Ova aplikacija može pristupati zvuku i reproducirati ga tokom poziva."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"Aplikacija <xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> je prestala reagirati"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Za poziv se koristila aplikacija za telefon koju ste dobili uz uređaj"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Zvuk poziva je isključen."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Meni za programere iz telekoma"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Pozivi se ne mogu primati tokom hitnog poziva"</string>
     <string name="cancel" msgid="6733466216239934756">"Otkaži"</string>
+    <string name="back" msgid="6915955601805550206">"Nazad"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Slušalica"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Žičane slušalice"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Zvučnik"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Vanjski"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Nepoznato"</string>
 </resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 3978d69..2c5727d 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"La trucada a <xliff:g id="CALLER">%s</xliff:g> s\'ha desconnectat perquè s\'ha fet una trucada d\'emergència."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"La teva trucada s\'ha desconnectat perquè s\'ha fet una trucada d\'emergència."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Trucada en segon pla"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> ha fet una trucada en segon pla. És possible que aquesta aplicació estigui accedint a l\'àudio i reproduint-lo a través de la trucada."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> està processant una trucada en segon pla. És possible que aquesta aplicació estigui accedint a l\'àudio i reproduint-lo a través de la trucada."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> ha deixat de respondre"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"A la trucada s\'ha fet servir l\'aplicació de telèfon que hi ha al dispositiu"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Trucada silenciada."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Menú per a desenvolupadors de telecomunicacions"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"No es poden respondre trucades durant una trucada d\'emergència."</string>
     <string name="cancel" msgid="6733466216239934756">"Cancel·la"</string>
+    <string name="back" msgid="6915955601805550206">"Enrere"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Auricular"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Auriculars amb cable"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Altaveu"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Extern"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Desconegut"</string>
 </resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 3b144aa..2945d28 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -30,13 +30,13 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Hovor s volajícím <xliff:g id="CALLER">%s</xliff:g> byl odpojen, protože začalo být prováděno tísňové volání."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Váš hovor byl odpojen, protože bylo zahájeno tísňové volání."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Hovor na pozadí"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"Aplikace <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> přesunula hovor na pozadí. Tato aplikace může v hovoru používat a přehrávat zvuk."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"Aplikace <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> zpracovává hovor na pozadí. Tato aplikace může v hovoru používat a přehrávat zvuk."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"Aplikace <xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> přestala reagovat"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Při hovoru byla použita předinstalovaná telefonní aplikace"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Hovor ztlumen."</string>
     <string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Reproduktor je zapnutý."</string>
     <string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Teď nemůžu mluvit, o co jde?"</string>
-    <string name="respond_via_sms_canned_response_2" msgid="2052951316129952406">"Zavolám zpátky."</string>
+    <string name="respond_via_sms_canned_response_2" msgid="2052951316129952406">"Zavolám později."</string>
     <string name="respond_via_sms_canned_response_3" msgid="6656147963478092035">"Zavolám později."</string>
     <string name="respond_via_sms_canned_response_4" msgid="9141132488345561047">"Nemůžu telefonovat. Zavoláš později?"</string>
     <string name="respond_via_sms_setting_title" msgid="4762275482898830160">"Rychlé odpovědi"</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Nabídka pro vývojáře Telecomu"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Během tísňového volání není možné přijímat hovory."</string>
     <string name="cancel" msgid="6733466216239934756">"Zrušit"</string>
+    <string name="back" msgid="6915955601805550206">"Zpět"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Sluchátko"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Kabelová náhlavní souprava"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Reproduktor"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Externí"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Není známo"</string>
 </resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 70a19ce..366b584 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Opkaldet til <xliff:g id="CALLER">%s</xliff:g> er blevet afbrudt, fordi der foretages et nødopkald."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Dit opkald er blevet afbrudt, fordi der foretages et nødopkald."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Opkald i baggrunden"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> har foretaget et opkald i baggrunden. Denne app har muligvis adgang til og afspiller lyd i opkaldet."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> behandler et opkald i baggrunden. Denne app har muligvis adgang til og afspiller lyd i opkaldet."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> svarer ikke"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Dit opkald brugte den opkaldsapp, din enhed er født med"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Lyd slået fra opkald."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Udviklermenu for Telecom"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Du kan ikke besvare opkald, mens du er i et nødopkald."</string>
     <string name="cancel" msgid="6733466216239934756">"Annuller"</string>
+    <string name="back" msgid="6915955601805550206">"Tilbage"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Højttaler"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Headset med ledning"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Højttaler"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Ekstern"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Ukendt"</string>
 </resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index e73ac10..801321b 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Der Anruf mit <xliff:g id="CALLER">%s</xliff:g> wurde beendet, weil ein Notruf getätigt wurde."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Der Anruf wurde beendet, weil ein Notruf getätigt wurde."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Anruf im Hintergrund"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> hat einen Anruf in den Hintergrund verschoben. Diese App greift möglicherweise auf den Anruf zu und spielt Audio darüber ab."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> verarbeitet einen Anruf im Hintergrund. Diese App greift möglicherweise auf den Anruf zu und spielt Audio darüber ab."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> reagiert nicht mehr"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Für den Anruf wurde die auf deinem Gerät vorinstallierte App verwendet"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Anruf stummgeschaltet"</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Telecom-Entwicklermenü"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Während eines Notrufs kannst du keine Anrufe annehmen."</string>
     <string name="cancel" msgid="6733466216239934756">"Abbrechen"</string>
+    <string name="back" msgid="6915955601805550206">"Zurück"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Kopfhörer"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Kabelgebundenes Headset"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Lautsprecher"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Extern"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Unbekannt"</string>
 </resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index b677874..7a09f0a 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Η κλήση προς <xliff:g id="CALLER">%s</xliff:g> αποσυνδέθηκε λόγω πραγματοποίησης κλήσης έκτακτης ανάγκης."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Η κλήση σας αποσυνδέθηκε λόγω πραγματοποίησης κλήσης έκτακτης ανάγκης."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Κλήση στο παρασκήνιο"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"Η εφαρμογή <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> πραγματοποίησε μια κλήση στο παρασκήνιο. Η εφαρμογή αυτή ενδέχεται να έχει δικαίωμα προσπέλασης και αναπαραγωγής ήχου κατά τη διάρκεια της κλήσης."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"Η εφαρμογή <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> επεξεργάζεται μια κλήση στο παρασκήνιο. Η εφαρμογή αυτή ενδέχεται να έχει δικαίωμα πρόσβασης και αναπαραγωγής ήχου κατά τη διάρκεια της κλήσης."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"Η εφαρμογή <xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> έπαψε να αποκρίνεται"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Η κλήση σας πραγματοποιήθηκε μέσω της αρχικής εφαρμογής τηλεφώνου της συσκευής σας."</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Η κλήση τέθηκε σε σίγαση."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Μενού προγραμματιστών τηλεπικοινωνιών"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Δεν είναι δυνατή η λήψη κλήσεων κατά τη διάρκεια κλήσης επείγουσας ανάγκης."</string>
     <string name="cancel" msgid="6733466216239934756">"Ακύρωση"</string>
+    <string name="back" msgid="6915955601805550206">"Πίσω"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Ακουστικό"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Ενσύρματα ακουστικά"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Ηχείο"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Εξωτερικά"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Άγνωστο"</string>
 </resources>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index cef6db5..0249401 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"The call to <xliff:g id="CALLER">%s</xliff:g> has been disconnected due to an emergency call being placed."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Your call has been disconnected due to an emergency call being placed."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Background call"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> has placed a call into the background. This app may be accessing and playing audio over the call."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> is processing a call in the background. This app may be accessing and playing audio over the call."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> stopped responding"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Your call used the phone app that came with your device"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Call muted."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Telecom Developer Menu"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Calls can not be taken while in an emergency call."</string>
     <string name="cancel" msgid="6733466216239934756">"Cancel"</string>
+    <string name="back" msgid="6915955601805550206">"Back"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Earpiece"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Wired headset"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Speaker"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"External"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Unknown"</string>
 </resources>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index cef6db5..5f857c1 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -16,7 +16,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Phone calls"</string>
+    <string name="telecommAppLabel" product="default" msgid="1825598513414129827">"Phone Calls"</string>
     <string name="userCallActivityLabel" product="default" msgid="3605391260292846248">"Phone"</string>
     <string name="unknown" msgid="6993977514360123431">"Unknown"</string>
     <string name="notification_missedCallTitle" msgid="5060387047205532974">"Missed call"</string>
@@ -30,13 +30,13 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"The call to <xliff:g id="CALLER">%s</xliff:g> has been disconnected due to an emergency call being placed."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Your call has been disconnected due to an emergency call being placed."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Background call"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> has placed a call into the background. This app may be accessing and playing audio over the call."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> is processing a call in the background. This app may be accessing and playing audio over the call."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> stopped responding"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Your call used the phone app that came with your device"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Call muted."</string>
     <string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Speakerphone enabled."</string>
-    <string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"I am so sorry, I can\'t answer the phone right now. How can I help you?"</string>
-    <string name="respond_via_sms_canned_response_2" msgid="2052951316129952406">"I\'ll call you back."</string>
+    <string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Can\'t talk now. What\'s up?"</string>
+    <string name="respond_via_sms_canned_response_2" msgid="2052951316129952406">"I\'ll call you right back."</string>
     <string name="respond_via_sms_canned_response_3" msgid="6656147963478092035">"I\'ll call you later."</string>
     <string name="respond_via_sms_canned_response_4" msgid="9141132488345561047">"Can\'t talk now. Call me later?"</string>
     <string name="respond_via_sms_setting_title" msgid="4762275482898830160">"Quick responses"</string>
@@ -47,7 +47,7 @@
     <string name="respond_via_sms_failure_format" msgid="5198680980054596391">"Message failed to send to <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
     <string name="enable_account_preference_title" msgid="6949224486748457976">"Calling accounts"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="3424338207838851646">"Only emergency calls are allowed."</string>
-    <string name="outgoing_call_not_allowed_no_permission" msgid="8590468836581488679">"This application cannot make outgoing calls without Phone permission."</string>
+    <string name="outgoing_call_not_allowed_no_permission" msgid="8590468836581488679">"This application cannot make outgoing calls without the Phone permission."</string>
     <string name="outgoing_call_error_no_phone_number_supplied" msgid="7665135102566099778">"To place a call, enter a valid number."</string>
     <string name="duplicate_video_call_not_allowed" msgid="5754746140185781159">"Call cannot be added at this time."</string>
     <string name="no_vm_number" msgid="2179959110602180844">"Missing voicemail number"</string>
@@ -56,10 +56,10 @@
     <string name="change_default_dialer_dialog_title" msgid="5861469279421508060">"Make <xliff:g id="NEW_APP">%s</xliff:g> your default Phone app?"</string>
     <string name="change_default_dialer_dialog_affirmative" msgid="8604665314757739550">"Set Default"</string>
     <string name="change_default_dialer_dialog_negative" msgid="8648669840052697821">"Cancel"</string>
-    <string name="change_default_dialer_warning_message" msgid="8461963987376916114">"<xliff:g id="NEW_APP">%s</xliff:g> will be able to place and control all aspects of calls. Only apps that you trust should be set as the default Phone app."</string>
+    <string name="change_default_dialer_warning_message" msgid="8461963987376916114">"<xliff:g id="NEW_APP">%s</xliff:g> will be able to place and control all aspects of calls. Only apps you trust should be set as the default Phone app."</string>
     <string name="change_default_call_screening_dialog_title" msgid="5365787219927262408">"Make <xliff:g id="NEW_APP">%s</xliff:g> your default call screening app?"</string>
     <string name="change_default_call_screening_warning_message_for_disable_old_app" msgid="2039830033533243164">"<xliff:g id="OLD_APP">%s</xliff:g> will no longer be able to screen calls."</string>
-    <string name="change_default_call_screening_warning_message" msgid="9020537562292754269">"<xliff:g id="NEW_APP">%s</xliff:g> will be able to see information about callers not in your contacts and will be able to block these calls. Only apps that you trust should be set as the default call screening app."</string>
+    <string name="change_default_call_screening_warning_message" msgid="9020537562292754269">"<xliff:g id="NEW_APP">%s</xliff:g> will be able to see information about callers not in your contacts and will be able to block these calls. Only apps you trust should be set as the default call screening app."</string>
     <string name="change_default_call_screening_dialog_affirmative" msgid="7162433828280058647">"Set Default"</string>
     <string name="change_default_call_screening_dialog_negative" msgid="1839266125623106342">"Cancel"</string>
     <string name="blocked_numbers" msgid="8322134197039865180">"Blocked numbers"</string>
@@ -73,13 +73,13 @@
     <string name="non_primary_user" msgid="315564589279622098">"Only the device owner can view and manage blocked numbers."</string>
     <string name="delete_icon_description" msgid="5335959254954774373">"Unblock"</string>
     <string name="blocked_numbers_butter_bar_title" msgid="582982373755950791">"Blocking temporarily off"</string>
-    <string name="blocked_numbers_butter_bar_body" msgid="1261213114919301485">"When you dial or text an emergency number, blocking is turned off to ensure that emergency services can contact you."</string>
+    <string name="blocked_numbers_butter_bar_body" msgid="1261213114919301485">"After you dial or text an emergency number, blocking is turned off to ensure that emergency services can contact you."</string>
     <string name="blocked_numbers_butter_bar_button" msgid="2704456308072489793">"Re-enable now"</string>
     <string name="blocked_numbers_number_blocked_message" msgid="4314736791180919167">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> blocked"</string>
     <string name="blocked_numbers_number_unblocked_message" msgid="2933071624674945601">"<xliff:g id="UNBLOCKED_NUMBER">%1$s</xliff:g> unblocked"</string>
     <string name="blocked_numbers_block_emergency_number_message" msgid="4198550501500893890">"Unable to block emergency number."</string>
     <string name="blocked_numbers_number_already_blocked_message" msgid="2301270825735665458">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> is already blocked."</string>
-    <string name="toast_personal_call_msg" msgid="5817631570381795610">"Using the personal dialler to make the call"</string>
+    <string name="toast_personal_call_msg" msgid="5817631570381795610">"Using the personal dialer to make the call"</string>
     <string name="notification_incoming_call" msgid="1233481138362230894">"<xliff:g id="CALL_VIA">%1$s</xliff:g> call from <xliff:g id="CALL_FROM">%2$s</xliff:g>"</string>
     <string name="notification_incoming_video_call" msgid="5795968314037063900">"<xliff:g id="CALL_VIA">%1$s</xliff:g> video call from <xliff:g id="CALL_FROM">%2$s</xliff:g>"</string>
     <string name="answering_ends_other_call" msgid="8653544281903986641">"Answering will end your <xliff:g id="CALL_VIA">%1$s</xliff:g> call"</string>
@@ -90,7 +90,7 @@
     <string name="answering_ends_other_managed_video_call" msgid="1988508241432031327">"Answering will end your ongoing video call"</string>
     <string name="answer_incoming_call" msgid="2045888814782215326">"Answer"</string>
     <string name="decline_incoming_call" msgid="922147089348451310">"Decline"</string>
-    <string name="cant_call_due_to_no_supported_service" msgid="1635626384149947077">"Call cannot be placed because there are no calling accounts that support calls of this type."</string>
+    <string name="cant_call_due_to_no_supported_service" msgid="1635626384149947077">"Call cannot be placed because there are no calling accounts which support calls of this type."</string>
     <string name="cant_call_due_to_ongoing_call" msgid="8004235328451385493">"Call cannot be placed due to your <xliff:g id="OTHER_CALL">%1$s</xliff:g> call."</string>
     <string name="cant_call_due_to_ongoing_calls" msgid="6379163795277824868">"Call cannot be placed due to your <xliff:g id="OTHER_CALL">%1$s</xliff:g> calls."</string>
     <string name="cant_call_due_to_ongoing_unknown_call" msgid="8243532328969433172">"Call cannot be placed due to a call in another app."</string>
@@ -101,7 +101,7 @@
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Disconnected calls"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Crashed phone apps"</string>
     <string name="alert_outgoing_call" msgid="5319895109298927431">"Placing this call will end your <xliff:g id="OTHER_APP">%1$s</xliff:g> call."</string>
-    <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Choose how to make this call"</string>
+    <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Choose how to place this call"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Redirect call using <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
     <string name="alert_place_unredirect_outgoing_call" msgid="2467608535225764006">"Call using my phone number"</string>
     <string name="alert_redirect_outgoing_call_timeout" msgid="5568101425637373060">"Call can\'t be placed by <xliff:g id="OTHER_APP">%1$s</xliff:g>. Try using a different call redirecting app or contacting the developer for help."</string>
@@ -109,8 +109,8 @@
     <string name="phone_settings_number_not_in_contact_txt" msgid="2602249106007265757">"Numbers not in Contacts"</string>
     <string name="phone_settings_number_not_in_contact_summary_txt" msgid="963327038085718969">"Block numbers that are not listed in your Contacts"</string>
     <string name="phone_settings_private_num_txt" msgid="6339272760338475619">"Private"</string>
-    <string name="phone_settings_private_num_summary_txt" msgid="6755758240544021037">"Block callers who do not disclose their number"</string>
-    <string name="phone_settings_payphone_txt" msgid="5003987966052543965">"Phonebox"</string>
+    <string name="phone_settings_private_num_summary_txt" msgid="6755758240544021037">"Block callers that do not disclose their number"</string>
+    <string name="phone_settings_payphone_txt" msgid="5003987966052543965">"Pay phone"</string>
     <string name="phone_settings_payphone_summary_txt" msgid="3936631076065563665">"Block calls from pay phones"</string>
     <string name="phone_settings_unknown_txt" msgid="3577926178354772728">"Unknown"</string>
     <string name="phone_settings_unknown_summary_txt" msgid="5446657192535779645">"Block calls from unidentified callers"</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Telecom Developer Menu"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Calls can not be taken while in an emergency call."</string>
     <string name="cancel" msgid="6733466216239934756">"Cancel"</string>
+    <string name="back" msgid="6915955601805550206">"Back"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Earpiece"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Wired headset"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Speaker"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"External"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Unknown"</string>
 </resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index cef6db5..0249401 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"The call to <xliff:g id="CALLER">%s</xliff:g> has been disconnected due to an emergency call being placed."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Your call has been disconnected due to an emergency call being placed."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Background call"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> has placed a call into the background. This app may be accessing and playing audio over the call."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> is processing a call in the background. This app may be accessing and playing audio over the call."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> stopped responding"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Your call used the phone app that came with your device"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Call muted."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Telecom Developer Menu"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Calls can not be taken while in an emergency call."</string>
     <string name="cancel" msgid="6733466216239934756">"Cancel"</string>
+    <string name="back" msgid="6915955601805550206">"Back"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Earpiece"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Wired headset"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Speaker"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"External"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Unknown"</string>
 </resources>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index cef6db5..0249401 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"The call to <xliff:g id="CALLER">%s</xliff:g> has been disconnected due to an emergency call being placed."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Your call has been disconnected due to an emergency call being placed."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Background call"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> has placed a call into the background. This app may be accessing and playing audio over the call."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> is processing a call in the background. This app may be accessing and playing audio over the call."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> stopped responding"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Your call used the phone app that came with your device"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Call muted."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Telecom Developer Menu"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Calls can not be taken while in an emergency call."</string>
     <string name="cancel" msgid="6733466216239934756">"Cancel"</string>
+    <string name="back" msgid="6915955601805550206">"Back"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Earpiece"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Wired headset"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Speaker"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"External"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Unknown"</string>
 </resources>
diff --git a/res/values-en-rXC/strings.xml b/res/values-en-rXC/strings.xml
index da9faf3..2ffae87 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‎‎‏‎‎‎‎‏‎‏‎‏‎‏‎‏‏‎‎‎‎‎‎‎‎‎‏‎‎‎‎‏‏‎‏‎‏‎‎‏‎‏‏‏‏‎‏‎‎‏‎‏‎‏‎‎‎‎‎‎‎‎‎The call to ‎‏‎‎‏‏‎<xliff:g id="CALLER">%s</xliff:g>‎‏‎‎‏‏‏‎ has been disconnected due to an emergency call being placed.‎‏‎‎‏‎"</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‎‎‏‎‎‏‎‏‎‏‎‎‎‎‎‎‏‎‎‏‏‎‏‏‏‏‎‏‏‏‎‏‎‎‎‏‎‎‎‎‏‏‏‏‎‎‎‏‏‏‏‏‏‏‎‏‎‏‎‏‎‏‎Your call has been disconnected due to an emergency call being placed.‎‏‎‎‏‎"</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‎‏‏‎‏‏‎‎‏‏‏‎‏‏‏‏‏‏‏‎‏‏‏‏‏‎‎‏‎‏‎‎‏‏‎‏‏‏‎‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‏‎‏‏‏‏‏‏‏‎Background call‎‏‎‎‏‎"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‎‏‏‎‎‎‏‏‎‎‎‏‏‎‏‎‏‏‏‎‏‎‏‏‏‏‏‏‎‎‎‏‎‏‏‏‎‎‎‏‎‏‏‎‏‏‏‏‏‎‏‎‎‎‏‎‏‎‎‏‎‎‎‎‏‎‎‏‏‎<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g>‎‏‎‎‏‏‏‎ has placed a call into the background. This app may be accessing and playing audio over the call.‎‏‎‎‏‎"</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‎‎‏‎‎‎‎‏‏‏‎‎‏‏‏‏‏‏‎‎‏‎‏‏‏‎‎‎‏‎‎‎‏‎‏‎‏‎‏‏‏‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‏‏‎<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g>‎‏‎‎‏‏‏‎ is processing a call in the background. This app may be accessing and playing audio over the call.‎‏‎‎‏‎"</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‎‎‏‎‏‎‎‎‏‏‎‏‏‎‎‏‎‏‎‏‏‏‎‏‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‎‏‏‏‏‏‎‎‏‏‎‎‎‎‏‎‏‎‎‎‎‏‎‎‏‏‎<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g>‎‏‎‎‏‏‏‎ stopped responding‎‏‎‎‏‎"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‎‎‎‎‏‎‎‏‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‎‏‏‎‏‏‏‏‎Your call used the phone app that came with your device‎‏‎‎‏‎"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‎‏‎‏‎‎‏‎‎‏‏‎‎‏‎‎‎‎‏‎‏‏‏‏‎‏‎‎‎‏‎‏‏‎‏‎‏‏‏‎‏‎‎‎‏‎‎‎‏‎‏‏‏‎‏‎‏‏‎‏‎‏‏‎Call muted.‎‏‎‎‏‎"</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‏‎‏‎‏‏‎‏‏‏‏‎‏‎‏‏‎‏‏‎‎‏‎‏‏‎‏‏‏‎‏‏‏‏‏‎‏‏‏‎‏‏‏‏‎‏‎‎‎‎‏‎Telecom Developer Menu‎‏‎‎‏‎"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‏‏‎‎‎‏‎‎‎‏‏‏‎‎‏‏‏‏‏‎‎‎‏‎‎‏‏‏‏‏‎‎‎‎‎‏‏‏‏‎‏‏‏‎‏‏‏‎‏‎‏‎Calls can not be taken while in an emergency call.‎‏‎‎‏‎"</string>
     <string name="cancel" msgid="6733466216239934756">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‎‏‏‏‎‏‎‏‏‏‎‎‏‎‎‎‎‏‎‎‏‏‏‏‎‏‏‎‎‏‎‎‏‎‎‏‏‎‎‎‎‏‎‎‏‏‎‎‏‎‏‏‎‏‎‎‏‎‎‏‎‎‎Cancel‎‏‎‎‏‎"</string>
+    <string name="back" msgid="6915955601805550206">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‎‏‎‎‎‏‏‏‏‏‎‏‎‎‎‏‏‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‎‎‎‏‎‎‏‏‏‏‏‏‎‎Back‎‏‎‎‏‎"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‎‎‏‏‎‎‏‏‏‏‏‏‎‎‎‏‏‏‎‎‎‏‎‏‎‎‎‎‏‏‎‏‎‏‏‏‎‏‎‏‎‏‏‎‎‎‏‎‎‎‎‎‏‎‎Earpiece‎‏‎‎‏‎"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‎‏‎‏‎‏‏‏‎‏‎‏‎‏‏‎‏‎‎‎‏‏‎‏‏‏‏‏‏‏‎‎‎‏‎‎‏‏‏‏‎‏‎‎‏‏‏‎‏‏‏‎‏‎‎‎‏‎‏‎‎‎Bluetooth‎‏‎‎‏‎"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‎‏‏‏‏‏‎‎‏‏‎‏‏‎‎‏‏‎‏‎‎‏‏‎‎‏‎‏‏‎‎‏‎‎‎‎‎‏‏‎‎‏‏‎‎‎‏‏‏‎‎‏‏‎‎‏‏‏‏‏‏‎‎Wired headset‎‏‎‎‏‎"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‎‏‏‏‎‏‏‎‏‎‏‏‏‎‏‎‎‎‏‏‎‎‏‏‎‏‏‎‎‏‏‎‎‎‎‏‏‏‎‏‎‏‏‏‏‏‎‎‎‎‏‎‎‏‎‎‎‏‏‎‏‎‏‎Speaker‎‏‎‎‏‎"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‎‏‎‎‎‎‎‎‏‏‏‎‎‎‎‏‏‎‎‏‏‏‎‏‎‏‎‏‏‎‏‎‏‎‏‎‎‏‎‎‎‏‎‎‏‏‏‏‎‎‎‏‏‏‎‎‏‏‏‏‎‎‎‎External‎‏‎‎‏‎"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‎‏‏‏‏‏‎‏‎‎‎‎‏‎‎‏‎‏‎‏‏‏‎‏‏‎‎‏‎‏‏‏‏‎‏‎‎‎‏‎‏‎‏‎‏‎‎‎‎‏‎‏‏‏‎‎‏‏‏‏‏‎‎‎Unknown‎‏‎‎‏‎"</string>
 </resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index ac0b22c..ab8f454 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -30,11 +30,11 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Se desconectó la llamada a <xliff:g id="CALLER">%s</xliff:g> porque se está realizando una llamada de emergencia."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Se desconectó tu llamada porque se está haciendo una llamada de emergencia."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Llamada en 2.° plano"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> movió una a segundo plano. Es posible que esta app acceda a la llamada y reproduzca audio."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> está procesando una llamada en segundo plano. Es posible que esta app acceda a la llamada y reproduzca audio."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> dejó de responder"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Tu llamada se hizo con la app de teléfono que venía en tu dispositivo"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Llamada silenciada"</string>
-    <string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Altavoz habilitado"</string>
+    <string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Bocina habilitada"</string>
     <string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"No puedo hablar ahora. ¿Todo bien?"</string>
     <string name="respond_via_sms_canned_response_2" msgid="2052951316129952406">"Te llamo enseguida."</string>
     <string name="respond_via_sms_canned_response_3" msgid="6656147963478092035">"Te llamo más tarde."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Menú para desarrolladores de Telecom"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"No puedes contestar llamadas mientras estés en una llamada de emergencia."</string>
     <string name="cancel" msgid="6733466216239934756">"Cancelar"</string>
+    <string name="back" msgid="6915955601805550206">"Atrás"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Auricular"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Auriculares con cable"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Bocina"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Externa"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Desconocido"</string>
 </resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 1d2aa15..65ab627 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Se ha interrumpido la llamada a <xliff:g id="CALLER">%s</xliff:g> debido a una llamada de emergencia."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Se ha interrumpido tu llamada debido a una llamada de emergencia."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Llamada en segundo plano"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> ha llamado en segundo plano. Puede que esta aplicación acceda al audio y lo reproduzca durante la llamada."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> está procesando una llamada en segundo plano. Puede que esta aplicación acceda al audio y lo reproduzca durante la llamada."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> ha dejado de responder"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"La llamada se utilizó la aplicación para teléfonos que venía con tu dispositivo"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Llamada silenciada"</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Menú para desarrolladores de telecomunicaciones"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"No se pueden responder llamadas durante una llamada de emergencia."</string>
     <string name="cancel" msgid="6733466216239934756">"Cancelar"</string>
+    <string name="back" msgid="6915955601805550206">"Atrás"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Auricular"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Auriculares con cable"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Altavoz"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Fuentes externas"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Desconocido"</string>
 </resources>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 4075aec..7d9ad7b 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Kõne helistajaga <xliff:g id="CALLER">%s</xliff:g> on katkestatud, kuna alustati hädaabikõnet."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Teie kõne katkestati, kuna alustati hädaabikõnet."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Taustal olev kõne"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> lülitas kõne taustale. See rakendus võib helile juurde pääseda ja seda kõne ajal esitada."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> töötleb taustal kõnet. See rakendus võib helile juurde pääseda ja seda kõne ajal esitada."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> lõpetas reageerimise"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Teie kõne kasutas teie seadmega kaasas olnud telefonirakendust"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Kõne on summutatud."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Teenuse Telecom arendaja menüü"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Hädaabikõne ajal ei saa kõnesid vastu võtta."</string>
     <string name="cancel" msgid="6733466216239934756">"Tühista"</string>
+    <string name="back" msgid="6915955601805550206">"Tagasi"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Kuular"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Juhtmega peakomplekt"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Kõlar"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Välised"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Teadmata"</string>
 </resources>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index 92f088c..64645a4 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"<xliff:g id="CALLER">%s</xliff:g> deitzaileari egiten ari zinen deia deskonektatu da larrialdi-dei bat egiten ari zarelako."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Deia deskonektatu egin da, larrialdi-dei bat egiten ari zarelako."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Atzeko planoko deia"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> aplikazioak atzeko planoan jarri du deia. Baliteke aplikazioak audioa atzitzea eta erreproduzitzea deian zehar."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> dei bat prozesatzen ari da atzeko planoan. Baliteke aplikazioak audioa atzitzea eta erreproduzitzea deian zehar."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"Erantzuteari utzi dio <xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> aplikazioak"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Gailuaren telefono-aplikazioarekin egin da deia"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Deiaren audioa desaktibatu da."</string>
@@ -59,7 +59,7 @@
     <string name="change_default_dialer_warning_message" msgid="8461963987376916114">"Deien aspektu guztiak erabili eta kontrolatu ahal izango ditu <xliff:g id="NEW_APP">%s</xliff:g> aplikazioak. Fidagarriak diren aplikazioak bakarrik erabili beharko lirateke Telefonoa aplikazio lehenetsi gisa."</string>
     <string name="change_default_call_screening_dialog_title" msgid="5365787219927262408">"Deiak iragazteko aplikazio lehenetsia <xliff:g id="NEW_APP">%s</xliff:g> izatea nahi duzu?"</string>
     <string name="change_default_call_screening_warning_message_for_disable_old_app" msgid="2039830033533243164">"Aurrerantzean, <xliff:g id="OLD_APP">%s</xliff:g> aplikazioak ezingo ditu iragazi deiak."</string>
-    <string name="change_default_call_screening_warning_message" msgid="9020537562292754269">"Kontaktuetan gordeta ez dituzun deitzaileei buruzko informazioa ikusteko eta dei horiek blokeatzeko gai izango da <xliff:g id="NEW_APP">%s</xliff:g>. Aplikazio fidagarriak soilik ezarri beharko lirateke deiak iragazteko aplikazio lehenetsi gisa."</string>
+    <string name="change_default_call_screening_warning_message" msgid="9020537562292754269">"Kontaktuetan gordeta ez dauzkazun deitzaileei buruzko informazioa ikusteko eta dei horiek blokeatzeko gai izango da <xliff:g id="NEW_APP">%s</xliff:g>. Aplikazio fidagarriak soilik ezarri beharko lirateke deiak iragazteko aplikazio lehenetsi gisa."</string>
     <string name="change_default_call_screening_dialog_affirmative" msgid="7162433828280058647">"Ezarri lehenetsi gisa"</string>
     <string name="change_default_call_screening_dialog_negative" msgid="1839266125623106342">"Utzi"</string>
     <string name="blocked_numbers" msgid="8322134197039865180">"Blokeatutako zenbakiak"</string>
@@ -70,7 +70,7 @@
     <string name="add_blocked_dialog_body" msgid="8599974422407139255">"Blokeatu zenbaki honetatik jasotzen diren deiak eta testu-mezuak:"</string>
     <string name="add_blocked_number_hint" msgid="8769422085658041097">"Telefono-zenbakia"</string>
     <string name="block_button" msgid="485080149164258770">"Blokeatu"</string>
-    <string name="non_primary_user" msgid="315564589279622098">"Gailuaren jabeak soilik ikus eta kudea ditzake blokeatutako zenbakiak."</string>
+    <string name="non_primary_user" msgid="315564589279622098">"Gailuaren jabeak soilik ikusi eta kudea ditzake blokeatutako zenbakiak."</string>
     <string name="delete_icon_description" msgid="5335959254954774373">"Desblokeatu"</string>
     <string name="blocked_numbers_butter_bar_title" msgid="582982373755950791">"Aldi baterako desgaitu da blokeatzeko aukera"</string>
     <string name="blocked_numbers_butter_bar_body" msgid="1261213114919301485">"Larrialdietarako zenbakia markatu ondoren, edo zenbait horretara testu-mezua bidali ondoren, desaktibatu egingo da blokeatzeko aukera, larrialdi-zerbitzuak zurekin harremanetan jarriko direla ziurtatzeko."</string>
@@ -81,13 +81,13 @@
     <string name="blocked_numbers_number_already_blocked_message" msgid="2301270825735665458">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> blokeatuta dago dagoeneko."</string>
     <string name="toast_personal_call_msg" msgid="5817631570381795610">"Telefono pertsonala erabiltzen ari zara deia egiteko"</string>
     <string name="notification_incoming_call" msgid="1233481138362230894">"<xliff:g id="CALL_VIA">%1$s</xliff:g> deia (deitzailea: <xliff:g id="CALL_FROM">%2$s</xliff:g>)"</string>
-    <string name="notification_incoming_video_call" msgid="5795968314037063900">"<xliff:g id="CALL_VIA">%1$s</xliff:g> bideo-deia (deitzailea: <xliff:g id="CALL_FROM">%2$s</xliff:g>)"</string>
+    <string name="notification_incoming_video_call" msgid="5795968314037063900">"<xliff:g id="CALL_VIA">%1$s</xliff:g> bideodeia (deitzailea: <xliff:g id="CALL_FROM">%2$s</xliff:g>)"</string>
     <string name="answering_ends_other_call" msgid="8653544281903986641">"Erantzuten baduzu, amaitu egingo da <xliff:g id="CALL_VIA">%1$s</xliff:g> deia"</string>
     <string name="answering_ends_other_calls" msgid="3702302838456922535">"Erantzuten baduzu, amaitu egingo dira <xliff:g id="CALL_VIA">%1$s</xliff:g> deiak"</string>
-    <string name="answering_ends_other_video_call" msgid="8572022039304239958">"Erantzuten baduzu, amaitu egingo da <xliff:g id="CALL_VIA">%1$s</xliff:g> bideo-deia"</string>
+    <string name="answering_ends_other_video_call" msgid="8572022039304239958">"Erantzuten baduzu, amaitu egingo da <xliff:g id="CALL_VIA">%1$s</xliff:g> bideodeia"</string>
     <string name="answering_ends_other_managed_call" msgid="4031778317409881805">"Erantzuten baduzu, amaitu egingo da oraingo deia"</string>
     <string name="answering_ends_other_managed_calls" msgid="3974069768615307659">"Erantzuten baduzu, amaitu egingo dira oraingo deiak"</string>
-    <string name="answering_ends_other_managed_video_call" msgid="1988508241432031327">"Erantzuten baduzu, amaitu egingo da oraingo bideo-deia"</string>
+    <string name="answering_ends_other_managed_video_call" msgid="1988508241432031327">"Erantzuten baduzu, amaitu egingo da oraingo bideodeia"</string>
     <string name="answer_incoming_call" msgid="2045888814782215326">"Erantzun"</string>
     <string name="decline_incoming_call" msgid="922147089348451310">"Baztertu"</string>
     <string name="cant_call_due_to_no_supported_service" msgid="1635626384149947077">"Ezin da egin deia, ez dagoelako mota honetako deiak onartzen duen deiak egiteko konturik."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Telekomunikazioen garatzaileen menua"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Ezin duzu hartu deirik larrialdi-dei bat abian den bitartean."</string>
     <string name="cancel" msgid="6733466216239934756">"Utzi"</string>
+    <string name="back" msgid="6915955601805550206">"Atzera"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Aurikularrak"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetootha"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Entzungailu kableduna"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Bozgorailua"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Kanpokoa"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Ezezaguna"</string>
 </resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 7425bfc..83c8034 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"به‌دلیل انجام تماسی اضطراری، تماس با <xliff:g id="CALLER">%s</xliff:g> قطع شده است."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"به‌دلیل برقراری تماس اضطراری، تماس شما قطع شده است."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"تماس پس‌زمینه"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> تماسی را در پس‌زمینه قرار داد. ممکن است این برنامه درحین تماس به صدا دسترسی پیدا کند و آن را پخش کند."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> درحال پردازش تماس در پس‌زمینه است. ممکن است این برنامه درحین تماس به صدا دسترسی پیدا کند و آن را پخش کند."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> متوقف شد"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"تماستان بااستفاده از برنامه تلفن ارائه‌شده در دستگاهتان برقرار شد"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"تماس نادیده گرفته شد."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"‏منوی برنامه‌نویس Telecom"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"درحین برقراری تماسی اضطراری، نمی‌توان به تماس‌ها پاسخ داد."</string>
     <string name="cancel" msgid="6733466216239934756">"لغو"</string>
+    <string name="back" msgid="6915955601805550206">"برگشتن"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"گوشی"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"بلوتوث"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"هدست سیمی"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"بلندگو"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"خارجی"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"نامشخص"</string>
 </resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index c04c43e..4ade7d1 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Puhelu vastaanottajalle <xliff:g id="CALLER">%s</xliff:g> on katkaistu hätäpuhelun soittamisen vuoksi."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Puhelusi on katkaistu hätäpuhelun soittamisen vuoksi."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Taustapuhelu"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> on asettanut puhelun taustalle. Tämä sovellus voi käyttää ja toistaa ääntä puhelun päällä."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> käsittelee puhelua taustalla. Tämä sovellus voi käyttää ja toistaa audiota puhelun päällä."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> lakkasi vastaamasta"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Puhelu soitettiin laitteen mukana tulleella puhelinsovelluksella"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Puhelu mykistetty."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Televiestinnän kehittäjävalikko"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Puheluita ei voi välittää hätäpuhelun aikana."</string>
     <string name="cancel" msgid="6733466216239934756">"Peru"</string>
+    <string name="back" msgid="6915955601805550206">"Takaisin"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Kuuloke"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Langallinen kuulokemikrofoni"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Kaiutin"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Ulkoinen"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Tuntematon"</string>
 </resources>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index 0a04f2c..95b2069 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"L\'appel à <xliff:g id="CALLER">%s</xliff:g> a été déconnecté en raison d\'un appel d\'urgence qui a été passé."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Votre appel a été déconnecté en raison d\'un appel d\'urgence en cours de lancement."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Appel en arrière-plan"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> a passé un appel en arrière-plan. Cette application peut accéder à l\'audio de l\'appel et faire jouer un contenu audio par l\'intermédiaire de l\'appel."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> traite un appel en arrière-plan. Cette application peut accéder à l\'audio de l\'appel et faire jouer un contenu audio par l\'intermédiaire de l\'appel."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> a arrêté de répondre"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Votre appel a utilisé l\'application Téléphone intégrée à votre appareil"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Son coupé"</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Menu Telecom Developer"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Les appels ne peuvent pas être pris pendant un appel d\'urgence."</string>
     <string name="cancel" msgid="6733466216239934756">"Annuler"</string>
+    <string name="back" msgid="6915955601805550206">"Retour"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Écouteur"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Casque d\'écoute filaire"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Haut-parleur"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Externe"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Inconnu"</string>
 </resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 4396bd5..03f6d87 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"L\'appel avec <xliff:g id="CALLER">%s</xliff:g> a été interrompu en raison d\'un appel d\'urgence."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Votre appel a été interrompu en raison d\'un appel d\'urgence."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Appel en arrière-plan"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> a placé un appel en arrière-plan. Il est possible que cette application lise des fichiers audio pendant l\'appel."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> est en train de traiter un appel en arrière-plan. Il est possible que cette application ait accès à l\'audio et le lise pendant l\'appel."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> a cessé de répondre"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"L\'appel s\'est poursuivi dans l\'application d\'appel préinstallée sur votre appareil"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Son coupé"</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Menu Telecom Developer"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Impossible de prendre un appel au cours d\'un appel d\'urgence."</string>
     <string name="cancel" msgid="6733466216239934756">"Annuler"</string>
+    <string name="back" msgid="6915955601805550206">"Retour"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Écouteur"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Casque filaire"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Haut-parleur"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Externe"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Inconnu"</string>
 </resources>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index ef3465d..a8443dd 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Desconectouse a chamada a <xliff:g id="CALLER">%s</xliff:g> porque se realizou unha chamada de emerxencia."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Desconectouse a chamada porque se realizou unha chamada de emerxencia."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Chamada seg. plano"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> chamou en segundo plano. Pode que esta aplicación acceda ao audio e o reproduza durante a chamada."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> está procesando unha chamada en segundo plano. Pode que esta aplicación acceda ao audio e o reproduza durante a chamada."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> deixou de responder"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Usouse a aplicación para teléfonos que ven co teu dispositivo na chamada"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Chamada silenciada"</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Menú para programadores de telecomunicacións"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Non se pode responder ás chamadas durante unha chamada de emerxencia."</string>
     <string name="cancel" msgid="6733466216239934756">"Cancelar"</string>
+    <string name="back" msgid="6915955601805550206">"Volver"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Auricular"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Auriculares con cable"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Altofalante"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Externo"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Descoñecido"</string>
 </resources>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 2729d22..4af6351 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"ઇમર્જન્સી કૉલને કારણે <xliff:g id="CALLER">%s</xliff:g>નો કૉલ ડિસ્કનેક્ટ કરવામાં આવ્યો છે."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"ઇમર્જન્સી કૉલને કારણે તમારો કૉલ ડિસ્કનેક્ટ કરવામાં આવ્યો છે."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"બૅકગ્રાઉન્ડ કૉલ"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g>એ કૉલ બેકગ્રાઉન્ડમાં રાખ્યો છે. આ ઍપ કૉલ પરથી ઑડિયો ઍક્સેસ કરીને તેને ચલાવી શકે છે."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"બૅકગ્રાઉન્ડમાં <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> કોઈ કૉલની પ્રક્રિયા કરી રહ્યું છે. આ ઍપ કૉલ પરથી ઑડિયો ઍક્સેસ કરીને તેને વગાડી શકે છે."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> ઍપ પ્રતિસાદ આપી રહી નથી"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"તમારા કૉલ માટે તમારા ડિવાઇસમાં પહેલેથી ઇન્સ્ટૉલ કરેલી ફોન ઍપનો ઉપયોગ કરવામાં આવ્યો"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"કૉલ મ્યૂટ કરેલ છે."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"ટેલિકોમ ડેવલપર મેનૂ"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"ઇમર્જન્સી કૉલ ચાલુ હોય, ત્યારે બીજા કોઈ કૉલ લઈ શકાતા નથી."</string>
     <string name="cancel" msgid="6733466216239934756">"રદ કરો"</string>
+    <string name="back" msgid="6915955601805550206">"પાછળ"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"ઇયરપીસ"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"બ્લૂટૂથ"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"વાયરવાળું હૅડસેટ"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"સ્પીકર"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"બાહ્ય"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"અજાણ"</string>
 </resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 3013ee9..918051a 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"<xliff:g id="CALLER">%s</xliff:g> के साथ चल रहे कॉल को डिसकनेक्ट किया गया क्योंकि एक आपातकालीन कॉल किया जा रहा है."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"एक आपातकालीन कॉल किया जा रहा है, इसलिए आपका कॉल डिसकनेक्ट कर दिया गया है."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"बैकग्राउंड कॉल"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> ने कॉल को बैकग्राउंड में रखा है हो सकता है कि यह ऐप्लिकेशन आपके कॉल से ऑडियो ऐक्सेस करके उसे चला रहा हो."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g>, बैकग्राउंड में कॉल को प्रोसेस कर रहा है. ऐसा हो सकता है कि यह ऐप्लिकेशन आपके कॉल से ऑडियो को ऐक्सेस करके चला रहा हो."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> ऐप्लिकेशन बंद हो गया है"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"कॉल करने के लिए, आपके डिवाइस में पहले से मौजूद फ़ोन ऐप्लिकेशन का इस्तेमाल किया गया"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"कॉल म्‍यूट की गई."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"टेलीकॉम डेवलपर मेन्यू"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"आपातकालीन कॉल के दौरान कॉल नहीं उठाया जा सकता."</string>
     <string name="cancel" msgid="6733466216239934756">"रद्द करें"</string>
+    <string name="back" msgid="6915955601805550206">"वापस जाएं"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"ईयरपीस"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"ब्लूटूथ"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"वायर वाला हेडसेट"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"स्पीकर"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"बाहरी सोर्स"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"कोई जानकारी नहीं है"</string>
 </resources>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index e84fe62..02c91fb 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Poziv upućen <xliff:g id="CALLER">%s</xliff:g> prekinut je zbog uspostavljanja hitnog poziva."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Poziv je prekinut zbog hitnog poziva."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Poziv u pozadini"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"Aplikacija <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> prebacila je poziv u pozadinu. Ta aplikacija možda pristupa zvuku i reproducira ga putem poziva."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> obrađuje poziv u pozadini. Ta aplikacija možda pristupa zvuku i reproducira ga putem poziva."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> više ne odgovara"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Vaš je poziv upotrijebio aplikaciju telefona koju ste dobili na uređaju"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Zvuk poziva isključen."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Izbornik Telecom Developer"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Pozivi se ne mogu primiti tijekom hitnog poziva."</string>
     <string name="cancel" msgid="6733466216239934756">"Odustani"</string>
+    <string name="back" msgid="6915955601805550206">"Natrag"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Slušalica"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Žičane slušalice"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Zvučnik"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Vanjski izvori"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Nepoznato"</string>
 </resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 75b8190..cdda34a 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Az Ön és <xliff:g id="CALLER">%s</xliff:g> közötti hívást a szolgáltató egy segélyhívás kezdeményezése miatt bontotta."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Segélyhívást kezdeményeztek, ezért az Ön hívását megszakítottuk."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Háttérbeli hívás"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"A(z) <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> a háttérbe helyezett egy hívást. Ez az alkalmazás hozzáférhet a híváshoz, és hangot játszhat le benne."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"A(z) <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> kezel egy hívást a háttérben. Ez az alkalmazás hozzáférhet a híváshoz, és hangot játszhat le benne."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"A(z) <xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> lefagyott"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"A hívása a készülékhez mellékelt telefonalkalmazást használta"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Hívás némítva."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Telekommunikációs fejlesztői menü"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Segélyhívás közben nem lehet hívást fogadni."</string>
     <string name="cancel" msgid="6733466216239934756">"Mégse"</string>
+    <string name="back" msgid="6915955601805550206">"Vissza"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Fülhallgató"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Vezetékes headset"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Hangszóró"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Külső"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Ismeretlen"</string>
 </resources>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index 43c0568..d85d037 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"<xliff:g id="CALLER">%s</xliff:g>-ի հետ ընթացիկ զանգն ընդհատվեց շտապ կանչի պատճառով։"</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Ձեր զանգն ընդհատվեց շտապ կանչի պատճառով։"</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Ֆոնային զանգ"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> հավելվածը տեղափոխեց զանգը ֆոնային ռեժիմ և կարող է զանգի ընթացքում աուդիո ֆայլ նվագարկել:"</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> հավելվածը ֆոնային ռեժիմում զանգ է մշակում և կարող է զանգի ընթացքում աուդիո ֆայլ նվագարկել։"</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> հավելվածը չի արձագանքում"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Զանգն իրականացվեց արտադրողի կողմից ձեր սարքում տեղադրված հեռախոսի հավելվածով։"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Զանգը խլացված է:"</string>
@@ -79,7 +79,7 @@
     <string name="blocked_numbers_number_unblocked_message" msgid="2933071624674945601">"<xliff:g id="UNBLOCKED_NUMBER">%1$s</xliff:g> արգելահանվեց"</string>
     <string name="blocked_numbers_block_emergency_number_message" msgid="4198550501500893890">"Արտակարգ իրավիճակների հեռախոսահամարը հնարավոր չէ արգելափակել:"</string>
     <string name="blocked_numbers_number_already_blocked_message" msgid="2301270825735665458">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> համարն արդեն արգելափակված է:"</string>
-    <string name="toast_personal_call_msg" msgid="5817631570381795610">"Զանգելու նպատակով անհատական համարհավաքիչի օգտագործում"</string>
+    <string name="toast_personal_call_msg" msgid="5817631570381795610">"Զանգելու նպատակով անհատական համարահավաքիչի օգտագործում"</string>
     <string name="notification_incoming_call" msgid="1233481138362230894">"<xliff:g id="CALL_VIA">%1$s</xliff:g>-ի զանգ՝ <xliff:g id="CALL_FROM">%2$s</xliff:g>-ից"</string>
     <string name="notification_incoming_video_call" msgid="5795968314037063900">"<xliff:g id="CALL_VIA">%1$s</xliff:g>-ի տեսազանգ՝ <xliff:g id="CALL_FROM">%2$s</xliff:g>-ից"</string>
     <string name="answering_ends_other_call" msgid="8653544281903986641">"Եթե պատասխանեք այս զանգին, <xliff:g id="CALL_VIA">%1$s</xliff:g>-ի ընթացիկ զանգը կընդհատվի"</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Telecom-ի մշակողի ընտրացանկ"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Շտապ կանչի ժամանակ այլ զանգեր չեք կարող ընդւնել։"</string>
     <string name="cancel" msgid="6733466216239934756">"Չեղարկել"</string>
+    <string name="back" msgid="6915955601805550206">"Հետ"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Լսափող"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Լարով ականջակալ"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Բարձրախոս"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Արտաքին"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Անհայտ"</string>
 </resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 2465076..84c0d39 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Panggilan kepada <xliff:g id="CALLER">%s</xliff:g> terputus karena ada panggilan darurat."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Panggilan Anda terputus karena ada panggilan darurat."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Panggilan latar belakang"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> telah menempatkan panggilan telepon ke latar belakang. Aplikasi ini mungkin mengakses dan memutar audio melalui panggilan telepon."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> sedang memproses panggilan di latar belakang. Aplikasi ini mungkin mengakses dan memutar audio dari panggilan telepon."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> berhenti merespons"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Panggilan Anda menggunakan aplikasi telepon bawaan perangkat Anda"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Panggilan disenyapkan."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Menu Developer Telecom"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Panggilan tidak dapat diterima saat sedang melakukan panggilan darurat."</string>
     <string name="cancel" msgid="6733466216239934756">"Batal"</string>
+    <string name="back" msgid="6915955601805550206">"Kembali"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Earpiece"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Headset berkabel"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Speaker"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Eksternal"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Tidak diketahui"</string>
 </resources>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index fe6cf7f..db7dbeb 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Símtalið til <xliff:g id="CALLER">%s</xliff:g> hefur verið aftengt vegna þess að neyðarsímtal var hringt."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Símtalið var aftengt vegna þess að neyðarsímtal var hringt."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Bakgrunnssímtal"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> hefur sett símtal í bakgrunn. Hugsanlega fær þetta forrit aðgang að hljóði yfir símtalið og spilar það."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> vinnur úr símtali í bakgrunni. Hugsanlega fær þetta forrit aðgang að hljóði yfir símtalið og spilar það."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> hætti að svara"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Símtalið þitt notaði símaforritið sem fylgdi tækinu"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Símtal þaggað."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Forritaravalmynd fyrir fjarskipti"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Ekki er hægt að svara símtölum meðan á neyðarsímtali stendur."</string>
     <string name="cancel" msgid="6733466216239934756">"Hætta við"</string>
+    <string name="back" msgid="6915955601805550206">"Til baka"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Eyrnatól"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Höfuðtól með snúru"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Hátalari"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Ytra tæki"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Óþekkt"</string>
 </resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index dd9ccbe..ad070d6 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"La chiamata a <xliff:g id="CALLER">%s</xliff:g> è stata disconnessa per dare priorità a una chiamata di emergenza."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"La tua chiamata è stata disconnessa per dare priorità a una chiamata di emergenza."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"In sottofondo"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> ha spostato una chiamata in sottofondo. L\'app potrà accedere all\'audio e riprodurlo in sovrapposizione alla chiamata."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> sta elaborando una chiamata in background. L\'app potrà accedere all\'audio e riprodurlo in sovrapposizione alla chiamata."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> non risponde più"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Per la tua chiamata è stata utilizzata l\'app per telefono integrata nel tuo dispositivo"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Chiamata disattivata."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Menu sviluppatore telecomunicazioni"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Impossibile accettare una chiamata durante una chiamata di emergenza."</string>
     <string name="cancel" msgid="6733466216239934756">"Annulla"</string>
+    <string name="back" msgid="6915955601805550206">"Indietro"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Auricolare"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Cuffie con cavo"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Vivavoce"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Esterno"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Sconosciuto"</string>
 </resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 2f37355..d557599 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"השיחה עם <xliff:g id="CALLER">%s</xliff:g> נותקה בגלל שיחת חירום."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"השיחה נותקה בגלל שיחת חירום."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"שיחה ברקע"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"האפליקציה <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> התחילה שיחה ברקע. ייתכן שלאפליקציה יש גישה לאודיו או שהיא משמיעה אודיו בשיחה."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"באפליקציית <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> נערכת שיחה ברקע. יכול להיות שלאפליקציה יש גישה לקול או שהיא משמיעה קול בשיחה."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"אפליקציית <xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> הפסיקה להגיב"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"השיחה הייתה דרך אפליקציית הטלפון המקורית של המכשיר"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"שיחה מושתקת."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"תפריט למפתחי מערכות תקשורת"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"אי אפשר לענות לשיחות אחרות בזמן שיחת חירום."</string>
     <string name="cancel" msgid="6733466216239934756">"ביטול"</string>
+    <string name="back" msgid="6915955601805550206">"חזרה"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"אוזניה"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"אוזניות חוטיות"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"רמקול"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"מכשיר חיצוני"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"לא ידוע"</string>
 </resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 2b000bb..73b85d9 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -24,13 +24,13 @@
     <string name="notification_missedCallsTitle" msgid="3910479625507893809">"不在着信"</string>
     <string name="notification_missedCallsMsg" msgid="5055782736170916682">"不在着信<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g>件"</string>
     <string name="notification_missedCallTicker" msgid="6731461957487087769">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g>さんからの不在着信"</string>
-    <string name="notification_missedCall_call_back" msgid="7900333283939789732">"コールバック"</string>
+    <string name="notification_missedCall_call_back" msgid="7900333283939789732">"かけ直す"</string>
     <string name="notification_missedCall_message" msgid="4054698824390076431">"メッセージ"</string>
     <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"通話が切断されました"</string>
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"緊急通報番号宛に発信中のため、<xliff:g id="CALLER">%s</xliff:g> さんとの通話が切断されました。"</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"緊急通報番号宛に発信中のため、通話が切断されました。"</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"バックグラウンド通話"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> が通話をバックグラウンドに切り替えました。これで、このアプリは通話を通じて音声にアクセスして再生できます。"</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> は通話をバックグラウンドで処理しています。このアプリは通話を通じて音声にアクセスして再生できます。"</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> が応答しなくなりました"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"通話には、ご利用のデバイスにプリインストールされていた通話アプリが使用されていました"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"通話がミュートされています。"</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Telecom デベロッパー メニュー"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"緊急通報中は、他の電話を受けることができません。"</string>
     <string name="cancel" msgid="6733466216239934756">"キャンセル"</string>
+    <string name="back" msgid="6915955601805550206">"戻る"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"受話口"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"有線ヘッドセット"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"スピーカー"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"外部"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"不明"</string>
 </resources>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index b6fe683..33c5a47 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"ზარი <xliff:g id="CALLER">%s</xliff:g>-თან გაითიშა გადაუდებელი ზარის განთავსების გამო."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"თქვენი ზარი გაითიშა, რადგან ხორციელდება გადაუდებელი ზარი."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"ზარი ფონში"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g>-მა განათავსა ზარი ფონში. ამ აპმა შეიძლება წვდომა იქონიოს აუდიოზე ზარიდან და დაუკრას ის."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> ამუშავებს ზარს ფონურ რეჟიმში. ამ აპმა შეიძლება წვდომა იქონიოს აუდიოზე ზარიდან და დაუკრას ის."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g>-მა რეაგირება შეწყვიტა"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"თქვენმა ზარმა გამოიყენა ტელეფონის აპი, რომელიც თქვენს მოწყობილობას მოჰყვა"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"ზარი დადუმებულია."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Telecom-ის დეველოპერის მენიუ"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"გადაუდებელი ზარის დროს ზარების მიღება შეუძლებელია."</string>
     <string name="cancel" msgid="6733466216239934756">"გაუქმება"</string>
+    <string name="back" msgid="6915955601805550206">"უკან"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"ყურმილი"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"სადენიანი ყურსაცვამი"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"დინამიკი"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"გარე"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"უცნობი"</string>
 </resources>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index aaa8229..7c07654 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Құтқару қызметіне қоңырау шалғандықтан, сіз бен <xliff:g id="CALLER">%s</xliff:g> арасындағы қоңырау ажыратылды."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Құтқару қызметіне қоңырау шалғандықтан, қоңырауыңыз ажыратылды."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Фондық қоңырау"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> қоңырауды фондық режимге ауыстырды. Бұл қолданба қоңырау барысында аудионы пайдалана және ойната алады."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> қоңырауды фондық режимде өңдеп жатыр. Бұл қолданба қоңырау барысында аудионы пайдалана және ойната алады."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> жауап бермейді"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Қоңырау құрылғының әдепкі телефон қолданбасы арқылы шалынды."</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Қоңырау үнсіздендірілген."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Telecom Developer мәзірі"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Құтқару қызметімен сөйлесіп жатқанда, басқа қоңырауларды қабылдай алмайсыз."</string>
     <string name="cancel" msgid="6733466216239934756">"Бас тарту"</string>
+    <string name="back" msgid="6915955601805550206">"Артқа"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Динамик"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Сымды гарнитура"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Динамик"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Сыртқы"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Белгісіз"</string>
 </resources>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index 82deb2a..64e47ef 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"ការហៅ​ទូរសព្ទ​ទៅ <xliff:g id="CALLER">%s</xliff:g> ត្រូវ​បាន​ផ្ដាច់ ដោយសារ​កំពុង​ហៅ​ទៅ​លេខ​សង្គ្រោះ​បន្ទាន់។"</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"ការហៅ​ទូរសព្ទ​របស់​អ្នក​ត្រូវ​បាន​ផ្ដាច់ ដោយសារ​តែ​កំពុង​ធ្វើ​ការហៅ​ទៅ​លេខ​សង្គ្រោះ​បន្ទាន់។"</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"ការហៅនៅផ្ទៃខាងក្រោយ"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> បាន​ធ្វើ​ការហៅ​ទូរសព្ទ​នៅ​ផ្ទៃ​ខាង​ក្រោយ។ កម្មវិធី​នេះ​អាច​កំពុង​ចូល​ប្រើប្រាស់ និង​ចាក់​សំឡេង​តាម​ការហៅ​ទូរសព្ទ។"</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> កំពុង​ដំណើរការ​ការ​ហៅ​ទូរសព្ទ​នៅ​ផ្ទៃខាងក្រោយ។ កម្មវិធី​នេះ​អាច​កំពុង​ចូល​ប្រើប្រាស់ និង​ចាក់​សំឡេង​តាម​ការហៅ​ទូរសព្ទ។"</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> បាន​គាំង"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"ការហៅ​ទូរសព្ទ​របស់​អ្នក​បាន​ប្រើប្រាស់​កម្មវិធី​ទូរសព្ទ​ដែល​ភ្ជាប់​មក​ជាមួយ​ឧបករណ៍​របស់​អ្នក"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"ការ​ហៅ​បិទ​សំឡេង។"</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"ម៉ឺនុយ​អ្នក​អភិវឌ្ឍន៍​ទូរគមនាគមន៍"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"មិន​អាច​ទទួល​ការហៅ​ទូរសព្ទ​បាន​ទេ ពេល​កំពុង​ហៅ​ទៅ​លេខ​សង្គ្រោះ​បន្ទាន់។"</string>
     <string name="cancel" msgid="6733466216239934756">"បោះបង់"</string>
+    <string name="back" msgid="6915955601805550206">"ថយក្រោយ"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"ឧបករណ៍ស្ដាប់សំឡេង"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"ប៊្លូធូស"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"កាស​មាន​ខ្សែ"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"ឧបករណ៍​បំពង​សំឡេង"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"ខាង​ក្រៅ"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"មិន​ស្គាល់"</string>
 </resources>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index bf5ea45..8109de2 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"ತುರ್ತು ಕರೆಯನ್ನು ಮಾಡುತ್ತಿರುವ ಕಾರಣ <xliff:g id="CALLER">%s</xliff:g> ಗೆ ಕರೆಯ ಕನೆಕ್ಷನ್ ಅನ್ನು ಕಡಿತಗೊಳಿಸಲಾಗಿದೆ."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"ತುರ್ತು ಕರೆಯನ್ನು ಮಾಡುತ್ತಿರುವ ಕಾರಣ ನಿಮ್ಮ ಕರೆಯನ್ನು ಕಡಿತಗೊಳಿಸಲಾಗಿದೆ."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"ಹಿನ್ನೆಲೆ ಕರೆ"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> ಆ್ಯಪ್, ಕರೆಯನ್ನು ಹಿನ್ನೆಲೆಯಲ್ಲಿ ಇರಿಸಿದೆ. ಈ ಆ್ಯಪ್ ಕರೆಯ ಮೂಲಕ ಆಡಿಯೊವನ್ನು ಪ್ರವೇಶಿಸಬಹುದು ಮತ್ತು ಪ್ಲೇ ಮಾಡಬಹುದು."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> ಹಿನ್ನೆಲೆಯಲ್ಲಿ ಕರೆಯನ್ನು ಪ್ರಕ್ರಿಯೆಗೊಳಿಸುತ್ತಿದೆ. ಈ ಆ್ಯಪ್ ಕರೆಯ ಮೂಲಕ ಆಡಿಯೊವನ್ನು ಪ್ರವೇಶಿಸಬಹುದು ಮತ್ತು ಪ್ಲೇ ಮಾಡಬಹುದು."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> ಪ್ರತಿಕ್ರಿಯಿಸುವುದನ್ನು ನಿಲ್ಲಿಸಿದೆ"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"ನಿಮ್ಮ ಕರೆಯು ಸಾಧನದ ಜೊತೆಗೆ ನೀಡಲಾದ ಫೋನ್ ಆ್ಯಪ್ ಅನ್ನು ಬಳಸಿದೆ."</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"ಕರೆಯನ್ನು ಮ್ಯೂಟ್ ಮಾಡಲಾಗಿದೆ."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"ಟೆಲಿಕಾಂ ಡೆವಲಪರ್ ಮೆನು"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"ತುರ್ತು ಕರೆಯಲ್ಲಿರುವಾಗ ಕರೆಗಳನ್ನು ಸ್ವೀಕರಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ."</string>
     <string name="cancel" msgid="6733466216239934756">"ರದ್ದುಮಾಡಿ"</string>
+    <string name="back" msgid="6915955601805550206">"ಹಿಂದಕ್ಕೆ"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"ಇಯರ್‌ಪೀಸ್"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"ಬ್ಲೂಟೂತ್"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"ವೈಯರ್ಡ್ ಹೆಡ್‌ಸೆಟ್"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"ಸ್ಪೀಕರ್"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"ಬಾಹ್ಯ"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"ಅಪರಿಚಿತ"</string>
 </resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index d51eb1c..6b4c2f1 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"긴급 전화 연결로 인해 <xliff:g id="CALLER">%s</xliff:g>님에게 건 통화가 연결 해제되었습니다."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"긴급 전화 연결로 인해 통화가 연결 해제되었습니다."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"백그라운드 통화"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g>에서 통화를 백그라운드로 전환했습니다. 앱이 통화에 사용되는 오디오에 액세스하거나 재생 중일 수 있습니다."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> 앱이 백그라운드에서 통화를 처리하는 중입니다. 앱이 통화에 사용되는 오디오에 액세스하거나 재생 중일 수 있습니다."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g>의 응답이 중지됨"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"기기의 기본 전화 앱을 사용하여 통화했습니다."</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"통화가 음소거되었습니다."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Telecom 개발자 메뉴"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"긴급 전화 중에는 전화를 받을 수 없습니다."</string>
     <string name="cancel" msgid="6733466216239934756">"취소"</string>
+    <string name="back" msgid="6915955601805550206">"뒤로"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"스피커"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"블루투스"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"유선 헤드셋"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"스피커"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"외부"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"알 수 없음"</string>
 </resources>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index ec00c40..aa8ce3e 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Учурда шашылыш чалуудан улам, <xliff:g id="CALLER">%s</xliff:g> чалуу ажыратылган."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Учурда шашылыш чалуудан улам, чалууңуз ажыратылган."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Фондогу чалуу"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> чалууну фонго койгон. Бул колдонмо чалуу аркылуу аудиого кирип, ойното алат."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> чалууну фондо иштетип жатат. Бул колдонмо чалуунун аудиосуна кирип, ойното алат."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> жооп берүүнү токтотту"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Чалууңуз түзмөгүңүз менен келген телефон колдонмосун пайдаланды"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Чалуу үнсүз тартипте."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Telecom иштеп чыгуучусунун менюсу"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Шашылыш учурунда чалуулар кабыл алынбайт."</string>
     <string name="cancel" msgid="6733466216239934756">"Жокко чыгаруу"</string>
+    <string name="back" msgid="6915955601805550206">"Артка"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Кулакчын"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Зымдуу гарнитура"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Динамик"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Тышкы"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Белгисиз"</string>
 </resources>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index 15cdb69..45c2b70 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"ສາຍໂທຫາ <xliff:g id="CALLER">%s</xliff:g> ຖືກຕັດແລ້ວ ເນື່ອງຈາກກຳລັງມີການໂທສຸກເສີນຢູ່."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"ສາຍໂທຂອງທ່ານໄດ້ຖືກຕັດແລ້ວ ເນື່ອງຈາກກຳລັງມີການໂທສຸກເສີນຢູ່."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"ການໂທໃນພື້ນຫຼັງ"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> ວາງການໂທໄວ້ພື້ນຫຼັງແລ້ວ. ແອັບນີ້ອາດເຂົ້າເຖິງ ແລະ ຫຼິ້ນສຽງຜ່ານການໂທໄດ້."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> ກຳລັງປະມວນຜົນການໂທຢູ່ພື້ນຫຼັງ. ແອັບນີ້ອາດເຂົ້າເຖິງ ແລະ ຫຼິ້ນສຽງຜ່ານການໂທໄດ້."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> ໄດ້ຢຸດການຕອບສະໜອງແລ້ວ"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"ການໂທຂອງທ່ານໃຊ້ແອັບໂທລະສັບທີ່ມາພ້ອມກັບອຸປະກອນຂອງທ່ານ"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"ປິດສຽງການໂທແລ້ວ."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"ເມນູນັກພັດທະນາໂທລະຄົມ"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"ບໍ່ສາມາດໂທໄດ້ໃນຂະນະທີ່ຢູ່ໃນການໂທສຸກເສີນ."</string>
     <string name="cancel" msgid="6733466216239934756">"ຍົກເລີກ"</string>
+    <string name="back" msgid="6915955601805550206">"ກັບຄືນ"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"ຫູຟັງ"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"ຊຸດຫູຟັງແບບມີສາຍ"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"ລຳໂພງ"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"ພາຍນອກ"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"ບໍ່ຮູ້ຈັກ"</string>
 </resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 2549087..5e8b1f2 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Skambutis kontaktui <xliff:g id="CALLER">%s</xliff:g> buvo atjungtas dėl atliekamo skambučio pagalbos numeriu."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Skambutis buvo atjungtas dėl atliekamo skambučio pagalbos numeriu."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Skambutis fone"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"Programa „<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g>“ perkėlė skambutį į foną. Ši programa gali pasiekti ir leisti garsą vykstant skambučiui."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"„<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g>“ apdoroja skambutį fone. Ši programa gali pasiekti ir leisti garsą vykstant skambučiui."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"Programa „<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g>“ nebereaguoja"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Atliekant skambutį buvo naudojama telefono programa, kuri buvo įrenginyje jį įsigijus"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Skambutis nutildytas."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Telekomunikacijų kūrėjų meniu"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Atliekant skambutį pagalbos numeriu negalima atsiliepti į kitus skambučius."</string>
     <string name="cancel" msgid="6733466216239934756">"Atšaukti"</string>
+    <string name="back" msgid="6915955601805550206">"Atgal"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Garsiakalbis prie ausies"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Laidinės ausinės"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Garsiakalbis"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Išoriniai šaltiniai"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Nežinoma"</string>
 </resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index b8d6b51..0433037 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Zvans lietotājam <xliff:g id="CALLER">%s</xliff:g> ir pārtraukts, jo tiek veikts ārkārtas izsaukums."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Jūsu zvans ir pārtraukts, jo tiek veikts ārkārtas izsaukums."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Saruna fonā"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> pārcēla sarunu uz darbību fonā. Sarunas laikā šī lietotne var piekļūt audio saturam un to atskaņot."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> apstrādā zvanu fonā. Sarunas laikā šī lietotne var piekļūt audio saturam un to atskaņot."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> pārtrauca reaģēt"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Zvanam tika izmantota jūsu ierīcē iebūvētā tālruņa lietotne"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Zvana skaņa ir izslēgta."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Telecom izstrādātāja izvēlne"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Ārkārtas izsaukuma laikā nevar pieņemt zvanus."</string>
     <string name="cancel" msgid="6733466216239934756">"Atcelt"</string>
+    <string name="back" msgid="6915955601805550206">"Atpakaļ"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Auss skaļrunis"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Vadu austiņas"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Skaļrunis"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Ārēja ierīce"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Nezināma ierīce"</string>
 </resources>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index c425177..4873380 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Повикот до <xliff:g id="CALLER">%s</xliff:g> се исклучи поради воспоставувањето итен повик."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Повикот ќе се исклучи поради воспоставувањето итен повик."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Повик во заднина"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> воспостави повик во заднината. Апликацијава можеби пристапува до и пушта аудио во повикот."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> обработува повик во заднината. Апликацијава можеби пристапува до и пушта аудио во повикот."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> падна"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Повикот се обави на апликацијата за телефон што дојде со уредот"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Повикот е со исклучен звук"</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Програмерско мени за телекомуникации"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Не може да примате повици во тек на итен повик."</string>
     <string name="cancel" msgid="6733466216239934756">"Откажи"</string>
+    <string name="back" msgid="6915955601805550206">"Назад"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Слушалка"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Жичени слушалки"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Звучник"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Надворешно"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Непознато"</string>
 </resources>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 47b4aa6c..9e6b8ca 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"ഒരു അടിയന്തര കോൾ നടക്കുന്നതിനാൽ <xliff:g id="CALLER">%s</xliff:g> എന്നയാൾക്ക് ചെയ്യുന്ന കോൾ വിച്‌ഛേദിക്കപ്പെട്ടിരിക്കുന്നു."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"ഒരു അടിയന്തര കോൾ നടക്കുന്നതിനാൽ നിങ്ങളുടെ കോൾ വിച്‌ഛേദിക്കപ്പെട്ടിരിക്കുന്നു."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"പശ്ചാത്തല കോൾ"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> കോൾ പശ്ചാത്തലത്തിലേക്ക് മാറ്റി. കോൾ ചെയ്യുമ്പോൾ ഈ ആപ്പിന് ഓഡിയോ ആക്‌സസ് ചെയ്യാനും പ്ലേ ചെയ്യാനും കഴിഞ്ഞേക്കാം."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> പശ്ചാത്തലത്തിൽ കോൾ പ്രോസസ് ചെയ്യുന്നു. കോൾ ചെയ്യുമ്പോൾ ഈ ആപ്പ് ഓഡിയോ ആക്‌സസ് ചെയ്ത് പ്ലേ ചെയ്തേക്കാം."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> ആപ്പ് പ്രതികരിക്കുന്നത് നിർത്തി"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"നിങ്ങളുടെ കോൾ, നിങ്ങളുടെ ഉപകരണത്തിനൊപ്പം ലഭ്യമായ ഫോൺ ആപ്പ് ഉപയോഗിച്ചു"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"കോൾ നിശബ്‌ദമാക്കി."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"ടെലികോം ഡെവലപ്പര്‍ മെനു"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"അടിയന്തര കോളിലായിരിക്കുമ്പോൾ കോളുകൾ എടുക്കാനാവില്ല."</string>
     <string name="cancel" msgid="6733466216239934756">"റദ്ദാക്കുക"</string>
+    <string name="back" msgid="6915955601805550206">"മടങ്ങുക"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"ഇയർഫോൺ"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"വയേർഡ് ഹെഡ്‌സെറ്റ്"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"സ്പീക്കർ"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"എക്സ്റ്റേണൽ സ്‌ട്രീമിംഗ്"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"അജ്ഞാതം"</string>
 </resources>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index 4ba44f3..2c90998 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Яаралтай дуудлага ирсэн тул <xliff:g id="CALLER">%s</xliff:g> руу хийсэн дуудлагыг салгалаа."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Яаралтай дуудлага ирсэн тул таны дуудлагыг салгалаа."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Арын дуудлага"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> дуудлагыг ард оруулсан байна. Энэхүү апп нь дуудлагаар аудиод хандаж, тоглуулж байна."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> нь дэвсгэрт дуудлага боловсруулж байна. Энэ апп дуудлагаар аудиод хандаж, мөн аудио тоглуулж байж магадгүй."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> хариу өгөхөө больсон"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Таны дуудлагыг таны төхөөрөмжтэй хамт ирсэн гар утасны аппаар хийсэн"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Дууг хаасан."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Телеком хөгжүүлэгчийн цэс"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Яаралтай дуудлагын үеэр дуудлага авах боломжгүй."</string>
     <string name="cancel" msgid="6733466216239934756">"Цуцлах"</string>
+    <string name="back" msgid="6915955601805550206">"Буцах"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Чихний спикер"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Утастай чихэвч"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Чанга яригч"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Гадны"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Тодорхойгүй"</string>
 </resources>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index d4ddf6d..263433d 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -30,10 +30,10 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"आणीबाणी कॉल केल्यामुळे <xliff:g id="CALLER">%s</xliff:g> ला केलेला कॉल डिस्कनेक्ट केला गेला."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"आणीबाणी कॉल केल्यामुळे तुमचा कॉल डिस्कनेक्ट केला गेला आहे."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"बॅकग्राउंड कॉल"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> यांनी कॉल बॅकग्राउंडवर ठेवला आहे हे अ‍ॅप कदाचित कॉलद्वारे ऑडिओ ॲक्सेस आणि प्ले करत आहे."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> बॅकग्राउंडमध्ये कॉलवर प्रक्रिया करत आहे. हे अ‍ॅप कदाचित कॉलमधील ऑडिओ ॲक्सेस आणि प्ले करत आहे."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> ने प्रतिसाद देणे थांबवले आहे"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"तुमच्या कॉलने डिव्हाइससोबत दिलेले फोन अ‍ॅप वापरले आहे"</string>
-    <string name="accessibility_call_muted" msgid="2968461092554300779">"कॉल नि.शब्‍द केला."</string>
+    <string name="accessibility_call_muted" msgid="2968461092554300779">"कॉल म्यूट केला."</string>
     <string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"स्‍पीकरफोन सक्षम केला."</string>
     <string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"आत्ता बोलू शकत नाही. कशासाठी कॉल केला होता?"</string>
     <string name="respond_via_sms_canned_response_2" msgid="2052951316129952406">"मी तुम्हाला परत कॉल करेन."</string>
@@ -70,15 +70,15 @@
     <string name="add_blocked_dialog_body" msgid="8599974422407139255">"यावरील कॉल आणि एसएमएस ब्लॉक करा"</string>
     <string name="add_blocked_number_hint" msgid="8769422085658041097">"फोन नंबर"</string>
     <string name="block_button" msgid="485080149164258770">"ब्लॉक करा"</string>
-    <string name="non_primary_user" msgid="315564589279622098">"फक्त डिव्हाइस मालक अवरोधित केलेले नंबर पाहू आणि व्यवस्थापित करू शकतो."</string>
+    <string name="non_primary_user" msgid="315564589279622098">"फक्त डिव्हाइस मालक ब्लॉक केलेले नंबर पाहू आणि व्यवस्थापित करू शकतो."</string>
     <string name="delete_icon_description" msgid="5335959254954774373">"ब्लॉक करा"</string>
-    <string name="blocked_numbers_butter_bar_title" msgid="582982373755950791">"अवरोधित करणे तात्पुरते बंद आहे"</string>
-    <string name="blocked_numbers_butter_bar_body" msgid="1261213114919301485">"तुम्ही एखादा आणीबाणी नंबर डायल केला किंवा त्यावर मजकूर पाठविल्यानंतर, आणीबाणी सेवा आपल्याशी संपर्क साधू शकतात हे सुनिश्चित करण्यासाठी अवरोधित करणे बंद करते."</string>
+    <string name="blocked_numbers_butter_bar_title" msgid="582982373755950791">"ब्लॉक करणे तात्पुरते बंद आहे"</string>
+    <string name="blocked_numbers_butter_bar_body" msgid="1261213114919301485">"तुम्ही एखादा आणीबाणी नंबर डायल केला किंवा त्यावर मेसेज पाठवल्यानंतर, आणीबाणी सेवा तुमच्याशी संपर्क साधू शकतात याची खात्री करण्यासाठी ब्लॉक करणे बंद केले जाते."</string>
     <string name="blocked_numbers_butter_bar_button" msgid="2704456308072489793">"आता पुन्हा-सक्षम करा"</string>
-    <string name="blocked_numbers_number_blocked_message" msgid="4314736791180919167">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> अवरोधित केला"</string>
+    <string name="blocked_numbers_number_blocked_message" msgid="4314736791180919167">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> ब्लॉक केला"</string>
     <string name="blocked_numbers_number_unblocked_message" msgid="2933071624674945601">"<xliff:g id="UNBLOCKED_NUMBER">%1$s</xliff:g> अनब्लॉक केला"</string>
-    <string name="blocked_numbers_block_emergency_number_message" msgid="4198550501500893890">"आणीबाणी नंबर अवरोधित करण्यात अक्षम."</string>
-    <string name="blocked_numbers_number_already_blocked_message" msgid="2301270825735665458">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> आधीपासून अवरोधित केला आहे."</string>
+    <string name="blocked_numbers_block_emergency_number_message" msgid="4198550501500893890">"आणीबाणी नंबर ब्लॉक करता आला नाही."</string>
+    <string name="blocked_numbers_number_already_blocked_message" msgid="2301270825735665458">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> आधीपासून ब्लॉक केला आहे."</string>
     <string name="toast_personal_call_msg" msgid="5817631570381795610">"कॉल करण्यासाठी वैयक्तिक डायलर वापरणे"</string>
     <string name="notification_incoming_call" msgid="1233481138362230894">"<xliff:g id="CALL_FROM">%2$s</xliff:g> कडील <xliff:g id="CALL_VIA">%1$s</xliff:g> मधील कॉल"</string>
     <string name="notification_incoming_video_call" msgid="5795968314037063900">"<xliff:g id="CALL_FROM">%2$s</xliff:g> कडील <xliff:g id="CALL_VIA">%1$s</xliff:g> मधील व्हिडिओ कॉल"</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"टेलिकॉम डेव्‍हलपर मेनू"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"आणीबाणी कॉल दरम्यान कॉल घेतला जाऊ शकत नाही."</string>
     <string name="cancel" msgid="6733466216239934756">"रद्द करा"</string>
+    <string name="back" msgid="6915955601805550206">"मागे जा"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"इअरपिस"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"ब्लूटूथ"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"वायर्ड हेडसेट"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"स्पीकर"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"बाह्य"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"अज्ञात"</string>
 </resources>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index c9aff1b..4a8d554 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Panggilan kepada <xliff:g id="CALLER">%s</xliff:g> telah diputuskan sambungannya kerana panggilan kecemasan sedang dibuat."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Panggilan anda telah diputuskan sambungan kerana panggilan kecemasan sedang dibuat."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Panggilan latar blkg"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> telah meletakkan panggilan dalam latar belakang Apl ini mungkin mengakses dan memainkan audio mengatasi panggilan."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> sedang memproses panggilan pada latar. Apl ini mungkin mengakses dan memainkan audio mengatasi panggilan."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> telah berhenti memberikan respons"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Panggilan anda telah menggunakan aplikasi telefon yang disertakan bersama peranti anda"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Panggilan diredam."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Menu Pembangun Telekom"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Panggilan tidak boleh dijawab semasa dalam panggilan kecemasan."</string>
     <string name="cancel" msgid="6733466216239934756">"Batal"</string>
+    <string name="back" msgid="6915955601805550206">"Kembali"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Alat dengar"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Set kepala berwayar"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Pembesar suara"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Luaran"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Tidak diketahui"</string>
 </resources>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index 616bc7c..3511bca 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"အရေးပေါ်ဖုန်းခေါ်ဆိုမှု ပြုလုပ်နေသောကြောင့် <xliff:g id="CALLER">%s</xliff:g> ထံသို့ ခေါ်ဆိုမှုကို ဖြတ်တောက်လိုက်သည်။"</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"အရေးပေါ်ဖုန်းခေါ်ဆိုမှု ပြုလုပ်နေသောကြောင့် သင်၏ ခေါ်ဆိုမှုကို ဖြတ်တောက်လိုက်သည်။"</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"နောက်ခံမှ ခေါ်ဆိုမှု"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> သည် ခေါ်ဆိုမှုကို နောက်ခံသို့ ထည့်လိုက်ပါသည်။ ဤအက်ပ်သည် ခေါ်ဆိုမှုမှတစ်ဆင့် အသံများကို သုံးခြင်းနှင့် ဖွင့်ခြင်းတို့ ပြုလုပ်နိုင်ပါသည်။"</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> သည် ခေါ်ဆိုမှုကို နောက်ခံတွင် လုပ်ဆောင်နေသည်။ ဤအက်ပ်သည် ခေါ်ဆိုမှုမှတစ်ဆင့် အသံသုံးခြင်းနှင့် ဖွင့်ခြင်းတို့ကို ပြုလုပ်နေခြင်း ဖြစ်နိုင်သည်။"</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> က တုံ့ပြန်ခြင်းကို ရပ်လိုက်သည်"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"သင့်စက်ပစ္စည်းနှင့် အတူပါလာသော ဖုန်းအက်ပ်သုံးပြီး ခေါ်ဆိုမှုကို ပြုလုပ်ထားသည်"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"နားထောင်ရုံသာ (စကားပြောပိတ်ထားသည်)"</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Telecom ဆော့ဖ်ဝဲအင်ဂျင်နီယာ မီနူး"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"အရေးပေါ်ခေါ်ဆိုမှု ပြုလုပ်နေစဉ် ဖုန်းမခေါ်နိုင်ပါ။"</string>
     <string name="cancel" msgid="6733466216239934756">"မလုပ်တော့"</string>
+    <string name="back" msgid="6915955601805550206">"နောက်သို့"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"တယ်လီဖုန်းနားခွက်"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"ဘလူးတုသ်"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"ကြိုးတပ် မိုက်ခွက်ပါနားကြပ်"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"စပီကာ"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"ပြင်ပ"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"မသိ"</string>
 </resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index a981507..fb4dc97 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Anropet til <xliff:g id="CALLER">%s</xliff:g> er koblet fra fordi et nødanrop ble utført."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Samtalen din ble brutt fordi et nødanrop ble utført."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Bakgrunnsanrop"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> har flyttet et anrop til bakgrunnen. Det kan hende denne appen bruker og spiller av lyd over anropet."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> behandler et anrop i bakgrunnen. Det kan hende denne appen bruker og spiller av lyd over anropet."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> sluttet å svare"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Anropet ble gjort med telefonappen som fulgte med enheten din"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Samtalelyd er kuttet."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Meny for telekommunikasjonsutviklere"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Du kan ikke besvare anrop mens du har et pågående nødanrop."</string>
     <string name="cancel" msgid="6733466216239934756">"Avbryt"</string>
+    <string name="back" msgid="6915955601805550206">"Gå tilbake"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Høyttaler (ørestykke)"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Hodetelefoner med ledning"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Høyttaler"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Ekstern"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Ukjent"</string>
 </resources>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 8741730..8c02676 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"आपत्‌कालीन कल गरिएको हुनाले <xliff:g id="CALLER">%s</xliff:g> लाई गरिएको कल विच्छेद गरियो।"</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"आपत्‌कालीन कल जारी रहेको हुनाले तपाईंको कल विच्छेद गरिएको छ।"</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"पृष्ठभूमिको कल"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> ले एउटा कल पृष्ठभूमिमा राखेको छ। कल गरेको बेला यो एपले अडियोमाथि पहुँच राखेर प्ले गरिरहेको हुन सक्छ।"</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> ले ब्याकग्राउन्डमा कुनै कल प्रोसेस गर्दै छ। यो एपले तपाईंको कलको अडियो प्रयोग गरिरहेको र सोही अडियो प्ले गरिरहेको हुन सक्छ।"</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> ले काम गर्न छाड्यो"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"कल गर्नका लागि तपाईंको डिभाइसमा पहिल्यैदेखि रहेको फोन एप प्रयोग गरियो"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"कल म्युट भयो।"</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"टेलिकमको विकासकर्ताको मेनु"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"आपत्‌कालीन कल चलिराखेको बेलामा अरु कल स्वीकार गर्न सकिँदैन।"</string>
     <string name="cancel" msgid="6733466216239934756">"रद्द गर्नुहोस्"</string>
+    <string name="back" msgid="6915955601805550206">"पछाडि"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"इयरपस"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"ब्लुटुथ"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"तारसहितको हेडसेट"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"स्पिकर"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"बाह्य"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"अज्ञात"</string>
 </resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index be67223..726ab60 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Het gesprek met <xliff:g id="CALLER">%s</xliff:g> is beëindigd omdat er een noodoproep is geplaatst."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Je gesprek is beëindigd omdat er een noodoproep is geplaatst."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Achtergrondgesprek"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> heeft een gesprek op de achtergrond geplaatst. Deze app kan audio openen en afspelen over het gesprek."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> verwerkt een gesprek op de achtergrond. Deze app kan audio openen en afspelen over het gesprek."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> reageert niet meer"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Voor je gesprek is de telefoon-app van je apparaat gebruikt"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Gesprek gedempt."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Telecomontwikkelaarsmenu"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Gesprekken kunnen niet worden aangenomen tijdens een noodoproep."</string>
     <string name="cancel" msgid="6733466216239934756">"Annuleren"</string>
+    <string name="back" msgid="6915955601805550206">"Terug"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Oortelefoon"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Bedrade headset"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Speaker"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Extern"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Onbekend"</string>
 </resources>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 5c62bb1..c25ec86 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"ଏକ ଜରୁରୀକାଳୀନ କଲ୍ କରାଯାଇଥିବାରୁ <xliff:g id="CALLER">%s</xliff:g>ଙ୍କୁ କରାଯାଇଥିବା କଲ୍ ବିଚ୍ଛିନ୍ନ କରାଯାଇଛି।"</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"ଏକ ଜରୁରୀକାଳୀନ କଲ୍ କରାଯାଉଥିବା ଯୋଗୁଁ ଆପଣଙ୍କ କଲ୍ ବିଚ୍ଛିନ୍ନ ହୋଇଯାଇଛି।"</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"ବ୍ୟାକ୍‌ଗ୍ରାଉଣ୍ଡ କଲ୍"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> ବ୍ୟାକ୍‌ଗ୍ରାଉଣ୍ଡରେ ଏକ କଲ୍ କରିଛି। ଏହା ଆପ୍ କଲ୍ ସମୟରେ ଅଡିଓ ଆକ୍ସେସ୍ କରିପାରେ ଏବଂ ଚଲେଇପାରେ।"</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> ପୃଷ୍ଠପଟରେ ଏକ କଲ ପ୍ରକ୍ରିୟାନ୍ୱିତ କରୁଛି। ଏହି ଆପ କଲ ସମୟରେ ଅଡିଓ ଆକ୍ସେସ ଏବଂ ପ୍ଲେ କରିପାରେ।"</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> ଉତ୍ତର ଦେଉନାହିଁ"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"ଆପଣଙ୍କ କଲ୍ ପାଇଁ ଆପଣଙ୍କ ଡିଭାଇସରେ ଥିବା ଫୋନ୍ ଆପ୍ ବ୍ୟବହାର କରାଯାଇଥିଲା"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"କଲ୍ ମ୍ୟୁଟ୍ କରାଯାଇଛି।"</string>
@@ -40,7 +40,7 @@
     <string name="respond_via_sms_canned_response_3" msgid="6656147963478092035">"ମୁଁ ଆପଣଙ୍କୁ ପରେ କଲ୍ କରିବି।"</string>
     <string name="respond_via_sms_canned_response_4" msgid="9141132488345561047">"ବର୍ତ୍ତମାନ କଥା ହୋ‌ଇପାରିବ ନାହିଁ। ମୋତେ ପରେ କଲ୍ କରିବେ?"</string>
     <string name="respond_via_sms_setting_title" msgid="4762275482898830160">"ଶୀଘ୍ର ଉତ୍ତର"</string>
-    <string name="respond_via_sms_setting_title_2" msgid="4914853536609553457">"ଶୀଘ୍ର ଉତ୍ତରକୁ ଏଡିଟ୍ କରନ୍ତୁ"</string>
+    <string name="respond_via_sms_setting_title_2" msgid="4914853536609553457">"ଶୀଘ୍ର ଉତ୍ତରକୁ ଏଡିଟ କରନ୍ତୁ"</string>
     <string name="respond_via_sms_setting_summary" msgid="8054571501085436868"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="6579353156073272157">"ଶୀଘ୍ର ଉତ୍ତର"</string>
     <string name="respond_via_sms_confirmation_format" msgid="2932395476561267842">"<xliff:g id="PHONE_NUMBER">%s</xliff:g>କୁ ମେସେଜ୍ ପଠାଗଲା।"</string>
@@ -55,13 +55,13 @@
     <string name="add_vm_number_str" msgid="5179510133063168998">"ନମ୍ବର୍ ଯୋଡ଼ନ୍ତୁ"</string>
     <string name="change_default_dialer_dialog_title" msgid="5861469279421508060">"<xliff:g id="NEW_APP">%s</xliff:g>କୁ ଆପଣଙ୍କ ଫୋନ୍‌ର ଡିଫଲ୍ଟ ଆପ୍ କରିବେ?"</string>
     <string name="change_default_dialer_dialog_affirmative" msgid="8604665314757739550">"ଡିଫଲ୍ଟ ସେଟ୍ କରନ୍ତୁ"</string>
-    <string name="change_default_dialer_dialog_negative" msgid="8648669840052697821">"ବାତିଲ୍‍ କରନ୍ତୁ"</string>
+    <string name="change_default_dialer_dialog_negative" msgid="8648669840052697821">"ବାତିଲ କରନ୍ତୁ"</string>
     <string name="change_default_dialer_warning_message" msgid="8461963987376916114">"<xliff:g id="NEW_APP">%s</xliff:g> କଲ୍ କରିବା ଏବଂ କଲ୍‌ର ସମସ୍ତ ଦିଗକୁ ନିୟନ୍ତ୍ରଣ କରିବାରେ ସକ୍ଷମ ହେବ। କେବଳ ନିଜର ଭରସାଯୋଗ୍ୟ ଆପ୍‌କୁ ଡିଫଲ୍ଟ ଫୋନ୍ ଆପ୍ ଭାବେ ସେଟ୍ କରିବା ଉଚିତ୍।"</string>
     <string name="change_default_call_screening_dialog_title" msgid="5365787219927262408">"<xliff:g id="NEW_APP">%s</xliff:g>କୁ ଆପଣଙ୍କ ଡିଫଲ୍ଟ କଲ୍ ସ୍କ୍ରିନିଂ ଆପ୍ କରିବେ?"</string>
     <string name="change_default_call_screening_warning_message_for_disable_old_app" msgid="2039830033533243164">"<xliff:g id="OLD_APP">%s</xliff:g> କଲ୍ ସ୍କ୍ରିନ୍‌ କରିପାରିରିବେ ନାହିଁ।"</string>
     <string name="change_default_call_screening_warning_message" msgid="9020537562292754269">"ଆପଣଙ୍କ ଯୋଗାଯୋଗରେ ନଥିବା କଲର୍‌ଙ୍କ ସମ୍ଵନ୍ଧରେ ସୂଚନା ଦେଖିବାକୁ <xliff:g id="NEW_APP">%s</xliff:g> ସକ୍ଷମ ହେବେ। କେବଳ ଆପଣ ବିଶ୍ଵାସ କରୁଥିବା ଆପ୍ସ ଡିଫଲ୍ଟ କଲ୍ ସ୍କ୍ରିନିଂ ଆପ୍ ଭାବରେ ସେଟ୍ ହେବା ଆବଶ୍ୟକ ଅଟେ।"</string>
     <string name="change_default_call_screening_dialog_affirmative" msgid="7162433828280058647">"ଡିଫଲ୍ଟ ସେଟ୍ କରନ୍ତୁ"</string>
-    <string name="change_default_call_screening_dialog_negative" msgid="1839266125623106342">"ବାତିଲ୍‌ କରନ୍ତୁ"</string>
+    <string name="change_default_call_screening_dialog_negative" msgid="1839266125623106342">"ବାତିଲ କରନ୍ତୁ"</string>
     <string name="blocked_numbers" msgid="8322134197039865180">"ବ୍ଲକ୍ କରାଯାଇଥିବା ନମ୍ବର୍"</string>
     <string name="blocked_numbers_msg" msgid="2797422132329662697">"ବ୍ଲକ୍ କରାଯାଇଥିବା ନମ୍ବର୍‌ରୁ ଆପଣ କଲ୍ କିମ୍ବା ଟେକ୍ସଟ୍ ଗ୍ରହଣ କରିପାରିବେ ନାହିଁ।"</string>
     <string name="block_number" msgid="3784343046852802722">"ଗୋଟିଏ ନମ୍ବର୍ ଯୋଡ଼ନ୍ତୁ"</string>
@@ -73,7 +73,7 @@
     <string name="non_primary_user" msgid="315564589279622098">"କେବଳ ଡିଭାଇସ୍‌ର ମାଲିକ ଅବରୋଧ କରାଯାଇଥିବା ନମ୍ବର୍‌କୁ ଦେଖିପାରିବେ ଓ ପରିଚାଳନା କରିପାରିବେ।"</string>
     <string name="delete_icon_description" msgid="5335959254954774373">"ଅନବ୍ଲକ୍ କରନ୍ତୁ"</string>
     <string name="blocked_numbers_butter_bar_title" msgid="582982373755950791">"ଅସ୍ଥାୟୀରୂପେ ଅବରୋଧ ଅଫ୍ ଅଛି"</string>
-    <string name="blocked_numbers_butter_bar_body" msgid="1261213114919301485">"ଆପଣ ଗୋଟିଏ ଜରୁରିକାଳୀନ ନମ୍ବର୍‌କୁ ଡାଏଲ୍ କିମ୍ବା ଟେକ୍ସଟ୍ କରିବା ପରେ, ଜରୁରିକାଳୀନ ସେବା ଆପଣଙ୍କୁ ଯୋଗାଯୋଗ କରିବାକୁ ସୁନିଶ୍ଚିତ କରିବା ପାଇଁ ଅବରୋଧକୁ ବନ୍ଦ କରିଦିଆଯାଇଥାଏ।"</string>
+    <string name="blocked_numbers_butter_bar_body" msgid="1261213114919301485">"ଆପଣ ଗୋଟିଏ ଜରୁରିକାଳୀନ ନମ୍ବରକୁ ଡାଏଲ କିମ୍ବା ଟେକ୍ସଟ କରିବା ପରେ, ଜରୁରିକାଳୀନ ସେବା ଆପଣଙ୍କୁ କଣ୍ଟାକ୍ଟ କରିପାରୁଛି କି ନାହିଁ ତାହା ସୁନିଶ୍ଚିତ କରିବା ପାଇଁ ଅବରୋଧକୁ ବନ୍ଦ କରି ଦିଆଯାଇଛି।"</string>
     <string name="blocked_numbers_butter_bar_button" msgid="2704456308072489793">"ବର୍ତ୍ତମାନ ପୁନଃସକ୍ଷମ କରନ୍ତୁ"</string>
     <string name="blocked_numbers_number_blocked_message" msgid="4314736791180919167">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> ବ୍ଲକ୍ କରାଯାଇଛି"</string>
     <string name="blocked_numbers_number_unblocked_message" msgid="2933071624674945601">"<xliff:g id="UNBLOCKED_NUMBER">%1$s</xliff:g> ଅନବ୍ଲକ୍ କରାଯାଇଛି"</string>
@@ -119,8 +119,15 @@
     <string name="phone_strings_call_blocking_turned_off_notification_title_txt" msgid="2895809176537908791">"କଲ୍‌ ବ୍ଲକିଂ"</string>
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="1713632946174016619">"କଲ୍ ବ୍ଲକିଂକୁ ଅକ୍ଷମ କରାଯାଇଛି"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"ଜରୁରିକାଳୀନ କଲ୍ କରାଗଲା"</string>
-    <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"ଜରୁରିକାଳୀନ ସହାୟତା କର୍ମଚାରୀମାନେ ଆପଣଙ୍କୁ ଯୋଗଯୋଗ କରିବା ପାଇଁ କଲ୍ ଅବରୋଧକୁ ଅକ୍ଷମ କରାଯାଇଛି।"</string>
+    <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"ଜରୁରିକାଳୀନ ଉତ୍ତରଦାତାମାନେ ଆପଣଙ୍କୁ କଣ୍ଟାକ୍ଟ କରିବା ପାଇଁ କଲ ଅବରୋଧକୁ ଅକ୍ଷମ କରାଯାଇଛି।"</string>
     <string name="developer_title" msgid="9146088855661672353">"ଟେଲେକମ୍ ଡେଭେଲପର୍ ମେନୁ"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"ଜରୁରୀକାଳୀନ କଲ୍ ବେଳେ ଅନ୍ୟ କଲ୍ ଉଠାଇ ପାରିବେ ନାହିଁ।"</string>
-    <string name="cancel" msgid="6733466216239934756">"ବାତିଲ୍ କରନ୍ତୁ"</string>
+    <string name="cancel" msgid="6733466216239934756">"ବାତିଲ କରନ୍ତୁ"</string>
+    <string name="back" msgid="6915955601805550206">"ପଛକୁ ଫେରନ୍ତୁ"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"ଇୟରପିସ"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"ବ୍ଲୁଟୁଥ"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"ତାରଯୁକ୍ତ ହେଡସେଟ"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"ସ୍ପିକର"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"ଏକ୍ସଟର୍ନଲ"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"ଅଜଣା"</string>
 </resources>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index bc3d7fa..65073e2 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"<xliff:g id="CALLER">%s</xliff:g> ਦੀ ਕਾਲ ਨੂੰ ਕਿਸੇ ਸੰਕਟਕਾਲੀਨ ਕਾਲ ਦੇ ਚਲਦੇ ਕੱਟ ਦਿੱਤਾ ਗਿਆ ਹੈ।"</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"ਕਿਸੇ ਸੰਕਟਕਾਲੀਨ ਕਾਲ ਕਰਕੇ ਤੁਹਾਡੀ ਕਾਲ ਕੱਟ ਦਿੱਤੀ ਗਈ ਹੈ।"</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Background call"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> has placed a call into the background. This app may be accessing and playing audio over the call."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> ਬੈਕਗ੍ਰਾਊਂਡ ਵਿੱਚ ਕਾਲ \'ਤੇ ਪ੍ਰਕਿਰਿਆ ਕਰ ਰਹੀ ਹੈ। ਹੋ ਸਕਦਾ ਹੈ ਕਿ ਇਹ ਐਪ ਕਾਲ \'ਤੇ ਆਡੀਓ ਤੱਕ ਪਹੁੰਚ ਕਰ ਕੇ ਚਲਾ ਰਹੀ ਹੋਵੇ।"</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> ਨੇ ਕੰਮ ਕਰਨਾ ਬੰਦ ਕਰ ਦਿੱਤਾ"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"ਤੁਹਾਡੀ ਕਾਲ ਨੇ ਤੁਹਾਡੇ ਡੀਵਾਈਸ \'ਤੇ ਪਹਿਲਾਂ ਤੋਂ ਸਥਾਪਤ ਫ਼ੋਨ ਐਪ ਦੀ ਵਰਤੋਂ ਕੀਤੀ"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"ਕਾਲ ਮਿਊਟ ਕੀਤੀ।"</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"ਟੈਲੀਕੋਮ ਵਿਕਾਸਕਾਰ ਮੀਨੂ"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"ਕਿਸੇ ਸੰਕਟਕਾਲੀਨ ਕਾਲ ਦੌਰਾਨ ਹੋਰ ਕਾਲਾਂ ਨਹੀਂ ਲਈਆਂ ਜਾ ਸਕਦੀਆਂ।"</string>
     <string name="cancel" msgid="6733466216239934756">"ਰੱਦ ਕਰੋ"</string>
+    <string name="back" msgid="6915955601805550206">"ਪਿੱਛੇ"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"ਈਅਰਪੀਸ"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"ਬਲੂਟੁੱਥ"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"ਤਾਰ ਵਾਲਾ ਹੈੱਡਸੈੱਟ"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"ਸਪੀਕਰ"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"ਬਾਹਰੀ"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"ਅਗਿਆਤ"</string>
 </resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 958412d..a10d29f 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Połączenie z rozmówcą <xliff:g id="CALLER">%s</xliff:g> zostało przerwane z powodu nawiązania połączenia alarmowego."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Połączenie zostało przerwane z powodu nawiązania połączenia alarmowego."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Połączenie w tle"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"Aplikacja <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> umieściła połączenie w tle. Może ona uzyskiwać dostęp do dźwięku i odtwarzać go podczas połączenia."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"Aplikacja <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> przetwarza połączenie w tle. Może ona uzyskiwać dostęp do dźwięku i odtwarzać go podczas połączenia."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"Aplikacja <xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> nie odpowiada"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Połączenie zostało zrealizowane za pomocą aplikacji do obsługi telefonu zainstalowanej na urządzeniu"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Połączenie wyciszone."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Menu programisty Telecom"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Nie można odbierać rozmów przy nawiązanym połączeniu alarmowym."</string>
     <string name="cancel" msgid="6733466216239934756">"Anuluj"</string>
+    <string name="back" msgid="6915955601805550206">"Wstecz"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Słuchawka"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Słuchawki przewodowe"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Głośnik"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Zewnętrzne"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Brak informacji"</string>
 </resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 462e6f6..0b279b4 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"A chamada para <xliff:g id="CALLER">%s</xliff:g> foi desligada porque foi efetuada uma chamada de emergência."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"A chamada foi desligada porque foi efetuada uma chamada de emergência."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Cham. segundo plano"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> colocou uma chamada em segundo plano. Esta app pode estar a aceder e a reproduzir áudio sobre a chamada."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> está a processar uma chamada em segundo plano. Esta app pode estar a aceder e a reproduzir áudio sobre a chamada."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> deixou de responder."</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"A chamada utilizou a app de telefone incluída com o dispositivo."</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Chamada sem som."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Menu do programador de telecomunicações"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Não é possível atender chamadas durante uma chamada de emergência."</string>
     <string name="cancel" msgid="6733466216239934756">"Cancelar"</string>
+    <string name="back" msgid="6915955601805550206">"Anterior"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Auricular"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Auscultadores com microfone integrado com fios"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Altifalante"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Externo"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Desconhecido"</string>
 </resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 9e9fb32..a5628c4 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"A chamada para <xliff:g id="CALLER">%s</xliff:g> foi desconectada porque uma chamada de emergência está sendo realizada."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Sua chamada foi desconectada porque uma chamada de emergência está sendo feita."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Chamada em 2º plano"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"O app <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> colocou uma chamada em segundo plano. Talvez ele esteja acessando e tocando o áudio durante a chamada."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> está processando uma chamada em segundo plano. Talvez ele esteja acessando e tocando o áudio durante a chamada."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"O app <xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> parou de responder"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"A chamada usou o aplicativo de telefone que veio com seu dispositivo"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Chamada sem áudio."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Menu do desenvolvedor de telecomunicação"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Durante uma chamada de emergência, não é possível transferir chamadas para o dispositivo."</string>
     <string name="cancel" msgid="6733466216239934756">"Cancelar"</string>
+    <string name="back" msgid="6915955601805550206">"Voltar"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Minifone de ouvido"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Fone de ouvido com fio"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Alto-falante"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Externo"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Desconhecido"</string>
 </resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index d0b56f4..2332d4d 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -24,15 +24,15 @@
     <string name="notification_missedCallsTitle" msgid="3910479625507893809">"Apeluri nepreluate"</string>
     <string name="notification_missedCallsMsg" msgid="5055782736170916682">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> apeluri nepreluate"</string>
     <string name="notification_missedCallTicker" msgid="6731461957487087769">"Apel nepreluat de la <xliff:g id="MISSED_CALL_FROM">%s</xliff:g>"</string>
-    <string name="notification_missedCall_call_back" msgid="7900333283939789732">"Sunați"</string>
+    <string name="notification_missedCall_call_back" msgid="7900333283939789732">"Sună"</string>
     <string name="notification_missedCall_message" msgid="4054698824390076431">"Mesaj"</string>
     <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"Apel deconectat"</string>
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Apelul către <xliff:g id="CALLER">%s</xliff:g> a fost deconectat deoarece a fost inițiat un apel de urgență."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Apelul a fost deconectat deoarece a fost inițiat un apel de urgență."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Apel în fundal"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> a inițiat un apel în fundal. Este posibil ca această aplicație să acceseze și să redea mesaje audio peste apel."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> procesează un apel în fundal. Se poate ca aplicația să acceseze și să redea mesaje audio peste apel."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> nu mai răspunde"</string>
-    <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Apelul dvs. a folosit aplicația Telefon instalată pe dispozitiv"</string>
+    <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Apelul tău a folosit aplicația Telefon instalată pe dispozitiv"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Apel cu sunet dezactivat."</string>
     <string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"Difuzor activat."</string>
     <string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"Nu pot acum. Despre ce e vorba?"</string>
@@ -40,7 +40,7 @@
     <string name="respond_via_sms_canned_response_3" msgid="6656147963478092035">"Sun eu mai târziu."</string>
     <string name="respond_via_sms_canned_response_4" msgid="9141132488345561047">"Nu pot acum. Vorbim mai târziu?"</string>
     <string name="respond_via_sms_setting_title" msgid="4762275482898830160">"Răspunsuri rapide"</string>
-    <string name="respond_via_sms_setting_title_2" msgid="4914853536609553457">"Editați răspunsurile rapide"</string>
+    <string name="respond_via_sms_setting_title_2" msgid="4914853536609553457">"Editează răspunsurile rapide"</string>
     <string name="respond_via_sms_setting_summary" msgid="8054571501085436868"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="6579353156073272157">"Răspuns rapid"</string>
     <string name="respond_via_sms_confirmation_format" msgid="2932395476561267842">"Mesajul a fost trimis la <xliff:g id="PHONE_NUMBER">%s</xliff:g>."</string>
@@ -48,33 +48,33 @@
     <string name="enable_account_preference_title" msgid="6949224486748457976">"Conturi pentru apelare"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="3424338207838851646">"Sunt permise doar apelurile de urgență."</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="8590468836581488679">"Această aplicație nu poate efectua apeluri fără permisiunea Telefon."</string>
-    <string name="outgoing_call_error_no_phone_number_supplied" msgid="7665135102566099778">"Pentru a apela, introduceți un număr valid."</string>
+    <string name="outgoing_call_error_no_phone_number_supplied" msgid="7665135102566099778">"Pentru a apela, introdu un număr valid."</string>
     <string name="duplicate_video_call_not_allowed" msgid="5754746140185781159">"Apelul nu poate fi adăugat în acest moment."</string>
     <string name="no_vm_number" msgid="2179959110602180844">"Lipsește numărul mesageriei vocale"</string>
     <string name="no_vm_number_msg" msgid="1339245731058529388">"Niciun număr de mesagerie vocală nu este stocat pe cardul SIM."</string>
-    <string name="add_vm_number_str" msgid="5179510133063168998">"Adăugați numărul"</string>
-    <string name="change_default_dialer_dialog_title" msgid="5861469279421508060">"Setați <xliff:g id="NEW_APP">%s</xliff:g> ca aplicație prestabilită a telefonului?"</string>
-    <string name="change_default_dialer_dialog_affirmative" msgid="8604665314757739550">"Setați ca prestabilită"</string>
-    <string name="change_default_dialer_dialog_negative" msgid="8648669840052697821">"Anulați"</string>
-    <string name="change_default_dialer_warning_message" msgid="8461963987376916114">"<xliff:g id="NEW_APP">%s</xliff:g> va putea iniția apeluri și va putea controla toate aspectele acestora. E recomandat să setați ca aplicație prestabilită a telefonului numai aplicații în care aveți încredere."</string>
-    <string name="change_default_call_screening_dialog_title" msgid="5365787219927262408">"Setați <xliff:g id="NEW_APP">%s</xliff:g> ca aplicație prestabilită de filtrare apeluri?"</string>
+    <string name="add_vm_number_str" msgid="5179510133063168998">"Adaugă numărul"</string>
+    <string name="change_default_dialer_dialog_title" msgid="5861469279421508060">"Setezi <xliff:g id="NEW_APP">%s</xliff:g> ca aplicație prestabilită a telefonului?"</string>
+    <string name="change_default_dialer_dialog_affirmative" msgid="8604665314757739550">"Setează ca prestabilită"</string>
+    <string name="change_default_dialer_dialog_negative" msgid="8648669840052697821">"Anulează"</string>
+    <string name="change_default_dialer_warning_message" msgid="8461963987376916114">"<xliff:g id="NEW_APP">%s</xliff:g> va putea iniția apeluri și va putea controla toate aspectele acestora. E recomandat să setezi ca aplicație prestabilită a telefonului numai aplicații în care ai încredere."</string>
+    <string name="change_default_call_screening_dialog_title" msgid="5365787219927262408">"Setezi <xliff:g id="NEW_APP">%s</xliff:g> ca aplicație prestabilită de filtrare apeluri?"</string>
     <string name="change_default_call_screening_warning_message_for_disable_old_app" msgid="2039830033533243164">"<xliff:g id="OLD_APP">%s</xliff:g> nu va mai putea să filtreze apelurile."</string>
-    <string name="change_default_call_screening_warning_message" msgid="9020537562292754269">"<xliff:g id="NEW_APP">%s</xliff:g> va putea să vadă informațiile despre apelanții care nu sunt în agenda dvs. și va putea să blocheze apelurile respective. E recomandat să setați ca aplicație prestabilită de filtrare a apelurilor numai aplicații în care aveți încredere."</string>
+    <string name="change_default_call_screening_warning_message" msgid="9020537562292754269">"<xliff:g id="NEW_APP">%s</xliff:g> va putea să vadă informațiile despre apelanții care nu sunt în agenda ta și va putea să blocheze apelurile respective. E recomandat să setezi ca aplicație prestabilită de filtrare a apelurilor numai aplicații în care ai încredere."</string>
     <string name="change_default_call_screening_dialog_affirmative" msgid="7162433828280058647">"Setați ca prestabilită"</string>
-    <string name="change_default_call_screening_dialog_negative" msgid="1839266125623106342">"Anulați"</string>
+    <string name="change_default_call_screening_dialog_negative" msgid="1839266125623106342">"Anulează"</string>
     <string name="blocked_numbers" msgid="8322134197039865180">"Numere blocate"</string>
-    <string name="blocked_numbers_msg" msgid="2797422132329662697">"Nu veți primi apeluri sau mesaje text de la numerele blocate."</string>
-    <string name="block_number" msgid="3784343046852802722">"Adăugați un număr"</string>
-    <string name="unblock_dialog_body" msgid="2723393535797217261">"Deblocați <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
-    <string name="unblock_button" msgid="8732021675729981781">"Deblocați"</string>
-    <string name="add_blocked_dialog_body" msgid="8599974422407139255">"Blocați apelurile și mesajele text de la"</string>
+    <string name="blocked_numbers_msg" msgid="2797422132329662697">"Nu vei primi apeluri sau mesaje text de la numerele blocate."</string>
+    <string name="block_number" msgid="3784343046852802722">"Adaugă un număr"</string>
+    <string name="unblock_dialog_body" msgid="2723393535797217261">"Deblochezi <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g>?"</string>
+    <string name="unblock_button" msgid="8732021675729981781">"Deblochează"</string>
+    <string name="add_blocked_dialog_body" msgid="8599974422407139255">"Blochează apelurile și mesajele text de la"</string>
     <string name="add_blocked_number_hint" msgid="8769422085658041097">"Număr de telefon"</string>
-    <string name="block_button" msgid="485080149164258770">"Blocați"</string>
+    <string name="block_button" msgid="485080149164258770">"Blochează"</string>
     <string name="non_primary_user" msgid="315564589279622098">"Numai proprietarul dispozitivului poate vedea și gestiona numerele blocate."</string>
-    <string name="delete_icon_description" msgid="5335959254954774373">"Deblocați"</string>
+    <string name="delete_icon_description" msgid="5335959254954774373">"Deblochează"</string>
     <string name="blocked_numbers_butter_bar_title" msgid="582982373755950791">"Blocarea este dezactivată temporar"</string>
-    <string name="blocked_numbers_butter_bar_body" msgid="1261213114919301485">"După ce formați un număr de urgență sau trimiteți un mesaj la acesta, blocarea este dezactivată pentru ca serviciile de urgență să vă poată contacta."</string>
-    <string name="blocked_numbers_butter_bar_button" msgid="2704456308072489793">"Reactivați acum"</string>
+    <string name="blocked_numbers_butter_bar_body" msgid="1261213114919301485">"După ce formezi un număr de urgență sau trimiți un mesaj la acesta, blocarea este dezactivată pentru ca serviciile de urgență să te poată contacta."</string>
+    <string name="blocked_numbers_butter_bar_button" msgid="2704456308072489793">"Reactivează acum"</string>
     <string name="blocked_numbers_number_blocked_message" msgid="4314736791180919167">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> este blocat"</string>
     <string name="blocked_numbers_number_unblocked_message" msgid="2933071624674945601">"<xliff:g id="UNBLOCKED_NUMBER">%1$s</xliff:g> este deblocat"</string>
     <string name="blocked_numbers_block_emergency_number_message" msgid="4198550501500893890">"Numărul de urgență nu poate fi blocat."</string>
@@ -82,14 +82,14 @@
     <string name="toast_personal_call_msg" msgid="5817631570381795610">"Utilizarea telefonului personal pentru a apela"</string>
     <string name="notification_incoming_call" msgid="1233481138362230894">"Apel <xliff:g id="CALL_VIA">%1$s</xliff:g> de la <xliff:g id="CALL_FROM">%2$s</xliff:g>"</string>
     <string name="notification_incoming_video_call" msgid="5795968314037063900">"Apel video <xliff:g id="CALL_VIA">%1$s</xliff:g> de la <xliff:g id="CALL_FROM">%2$s</xliff:g>"</string>
-    <string name="answering_ends_other_call" msgid="8653544281903986641">"Dacă răspundeți, apelul dvs. <xliff:g id="CALL_VIA">%1$s</xliff:g> va fi încheiat."</string>
-    <string name="answering_ends_other_calls" msgid="3702302838456922535">"Dacă răspundeți, apelurile dvs. <xliff:g id="CALL_VIA">%1$s</xliff:g> vor fi încheiate."</string>
-    <string name="answering_ends_other_video_call" msgid="8572022039304239958">"Dacă răspundeți, apelul video <xliff:g id="CALL_VIA">%1$s</xliff:g> va fi încheiat."</string>
-    <string name="answering_ends_other_managed_call" msgid="4031778317409881805">"Dacă răspundeți, apelul în curs va fi încheiat."</string>
-    <string name="answering_ends_other_managed_calls" msgid="3974069768615307659">"Dacă răspundeți, apelurile în curs vor fi încheiate."</string>
-    <string name="answering_ends_other_managed_video_call" msgid="1988508241432031327">"Dacă răspundeți, apelul video în curs va fi încheiat."</string>
-    <string name="answer_incoming_call" msgid="2045888814782215326">"Răspundeți"</string>
-    <string name="decline_incoming_call" msgid="922147089348451310">"Respingeți"</string>
+    <string name="answering_ends_other_call" msgid="8653544281903986641">"Dacă răspunzi, apelul tău <xliff:g id="CALL_VIA">%1$s</xliff:g> va fi încheiat."</string>
+    <string name="answering_ends_other_calls" msgid="3702302838456922535">"Dacă răspunzi, apelurile tale <xliff:g id="CALL_VIA">%1$s</xliff:g> vor fi încheiate."</string>
+    <string name="answering_ends_other_video_call" msgid="8572022039304239958">"Dacă răspunzi, apelul video <xliff:g id="CALL_VIA">%1$s</xliff:g> va fi încheiat."</string>
+    <string name="answering_ends_other_managed_call" msgid="4031778317409881805">"Dacă răspunzi, apelul în curs va fi încheiat."</string>
+    <string name="answering_ends_other_managed_calls" msgid="3974069768615307659">"Dacă răspunzi, apelurile în curs vor fi încheiate."</string>
+    <string name="answering_ends_other_managed_video_call" msgid="1988508241432031327">"Dacă răspunzi, apelul video în curs va fi încheiat."</string>
+    <string name="answer_incoming_call" msgid="2045888814782215326">"Răspunde"</string>
+    <string name="decline_incoming_call" msgid="922147089348451310">"Respinge"</string>
     <string name="cant_call_due_to_no_supported_service" msgid="1635626384149947077">"Apelul nu poate fi inițiat deoarece nu există conturi pentru apelare compatibile cu apeluri de acest tip."</string>
     <string name="cant_call_due_to_ongoing_call" msgid="8004235328451385493">"Apelul nu poate fi inițiat din cauza apelului <xliff:g id="OTHER_CALL">%1$s</xliff:g>."</string>
     <string name="cant_call_due_to_ongoing_calls" msgid="6379163795277824868">"Apelul nu poate fi inițiat din cauza apelurilor <xliff:g id="OTHER_CALL">%1$s</xliff:g>."</string>
@@ -100,27 +100,34 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Apeluri în fundal"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"Apeluri deconectate"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"Aplicații pentru telefon blocate"</string>
-    <string name="alert_outgoing_call" msgid="5319895109298927431">"Dacă inițiați acest apel, cel din <xliff:g id="OTHER_APP">%1$s</xliff:g> va fi încheiat."</string>
-    <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Alegeți cum vreți să inițiați apelul"</string>
-    <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Redirecționați apelul folosind <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
+    <string name="alert_outgoing_call" msgid="5319895109298927431">"Dacă inițiezi acest apel, cel din <xliff:g id="OTHER_APP">%1$s</xliff:g> va fi încheiat."</string>
+    <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"Alege cum vrei să inițiezi apelul"</string>
+    <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"Redirecționezi apelul folosind <xliff:g id="OTHER_APP">%1$s</xliff:g>"</string>
     <string name="alert_place_unredirect_outgoing_call" msgid="2467608535225764006">"Apelează folosind numărul meu"</string>
-    <string name="alert_redirect_outgoing_call_timeout" msgid="5568101425637373060">"Apelul nu poate fi inițiat de <xliff:g id="OTHER_APP">%1$s</xliff:g>. Încercați să folosiți o aplicație de redirecționare a apelurilor sau să contactați dezvoltatorul pentru a solicita ajutorul."</string>
+    <string name="alert_redirect_outgoing_call_timeout" msgid="5568101425637373060">"Apelul nu poate fi inițiat de <xliff:g id="OTHER_APP">%1$s</xliff:g>. Încearcă să folosești o aplicație de redirecționare a apelurilor sau să contactezi dezvoltatorul pentru a solicita ajutorul."</string>
     <string name="phone_settings_call_blocking_txt" msgid="7311523114822507178">"Blocarea apelurilor"</string>
     <string name="phone_settings_number_not_in_contact_txt" msgid="2602249106007265757">"Numere care nu sunt în Agendă"</string>
-    <string name="phone_settings_number_not_in_contact_summary_txt" msgid="963327038085718969">"Blocați numerele care nu sunt înregistrate în Agendă"</string>
+    <string name="phone_settings_number_not_in_contact_summary_txt" msgid="963327038085718969">"Blochează numerele care nu sunt înregistrate în Agendă"</string>
     <string name="phone_settings_private_num_txt" msgid="6339272760338475619">"Privat"</string>
-    <string name="phone_settings_private_num_summary_txt" msgid="6755758240544021037">"Blocați apelanții care nu își afișează numărul"</string>
+    <string name="phone_settings_private_num_summary_txt" msgid="6755758240544021037">"Blochează apelanții care nu își afișează numărul"</string>
     <string name="phone_settings_payphone_txt" msgid="5003987966052543965">"Telefon public"</string>
-    <string name="phone_settings_payphone_summary_txt" msgid="3936631076065563665">"Blocați apelurile de la telefoane publice"</string>
+    <string name="phone_settings_payphone_summary_txt" msgid="3936631076065563665">"Blochează apelurile de la telefoane publice"</string>
     <string name="phone_settings_unknown_txt" msgid="3577926178354772728">"Necunoscut"</string>
-    <string name="phone_settings_unknown_summary_txt" msgid="5446657192535779645">"Blocați apelurile de la apelanți neidentificați"</string>
+    <string name="phone_settings_unknown_summary_txt" msgid="5446657192535779645">"Blochează apelurile de la apelanți neidentificați"</string>
     <string name="phone_settings_unavailable_txt" msgid="825918186053980858">"Indisponibil"</string>
-    <string name="phone_settings_unavailable_summary_txt" msgid="8221686031038282633">"Blocați apelurile unde numărul de telefon este indisponibil"</string>
+    <string name="phone_settings_unavailable_summary_txt" msgid="8221686031038282633">"Blochează apelurile unde numărul de telefon este indisponibil"</string>
     <string name="phone_strings_call_blocking_turned_off_notification_title_txt" msgid="2895809176537908791">"Blocarea apelurilor"</string>
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="1713632946174016619">"Blocarea apelurilor este dezactivată."</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"S-a efectuat un apel de urgență."</string>
-    <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Blocarea apelurilor a fost dezactivată pentru a permite serviciilor de urgență să vă contacteze."</string>
+    <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"Blocarea apelurilor a fost dezactivată pentru a permite serviciilor de urgență să te contacteze."</string>
     <string name="developer_title" msgid="9146088855661672353">"Meniu pentru dezvoltatori de telecomunicații"</string>
-    <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Nu puteți răspunde la apeluri în timpul unui apel de urgență."</string>
-    <string name="cancel" msgid="6733466216239934756">"Anulați"</string>
+    <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Nu poți răspunde la apeluri în timpul unui apel de urgență."</string>
+    <string name="cancel" msgid="6733466216239934756">"Anulează"</string>
+    <string name="back" msgid="6915955601805550206">"Înapoi"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Cască"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Set de căști-microfon cu fir"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Difuzor"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Extern"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Necunoscut"</string>
 </resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 6382c7b..139108d 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Вызов абонента <xliff:g id="CALLER">%s</xliff:g> был прекращен, так как осуществляется экстренный вызов."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Ваш вызов прекращен, так как осуществляется экстренный вызов."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Фоновый вызов"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"Приложение <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> перевело вызов в фоновый режим. Это приложение может получать доступ к аудио вызова или воспроизводить в нем свое аудио."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> обрабатывает звонок в фоновом режиме. Это приложение может получать доступ к аудио вызова или воспроизводить в нем свое аудио."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> не отвечает"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Вызов был сделан с использованием приложения, которое установлено на ваше устройство производителем."</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Звук выключен."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Меню разработчика Telecom"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Невозможно принять вызов, когда уже выполняется экстренный вызов."</string>
     <string name="cancel" msgid="6733466216239934756">"Отмена"</string>
+    <string name="back" msgid="6915955601805550206">"Назад"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Динамик телефона"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Проводная гарнитура"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Динамик"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Внешнее устройство"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Неизвестно"</string>
 </resources>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index 6cbaaa5..e3faf49 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"<xliff:g id="CALLER">%s</xliff:g> වෙත ඇමතුම හදිසි ඇමතුමක් ගනිමින් ඇති නිසා විසන්ධි කර ඇත."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"හදිසි ඇමතුමක් ගනිමින් පැවතීම හේතුවෙන් ඔබේ ඇමතුම විසන්ධි කර ඇත."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"පසුබිම් ඇමතුම"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> පසුබිම තුළට ඇමතුමක් ගෙන ඇත. මෙම යෙදුම ඇමතුම හරහා ඕඩියෝ වෙත ප්‍රවේශ වෙමින් සහ වාදනය කරමින් සිටිය හැකිය."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> පසුබිමේ ඇමතුමක් සකසමින් සිටී. මෙම යෙදුම ඇමතුම හරහා ඕඩියෝ වෙත ප්‍රවේශ වෙමින් සහ වාදනය කරමින් සිටිය හැක."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> ප්‍රතිචාර දැක්වීම නතර කළේය"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"ඔබේ ඇමතුම ඔබේ උපාංගය සමග පැමිණි දුරකථන යෙදුම භාවිත කළේය"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"ඇමතුම නිශ්ශබ්දයි."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"ටෙලිකොම් සංවර්ධක මෙනුව"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"හදිසි ඇමතුමක් අතරතුර ඇමතුම් ගත නොහැකිය."</string>
     <string name="cancel" msgid="6733466216239934756">"අවලංගු කරන්න"</string>
+    <string name="back" msgid="6915955601805550206">"ආපසු"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"සවන් කඩ"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"බ්ලූටූත්"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"රැහැන්ගත කළ හෙඩ්සෙට්"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"ස්පීකරය"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"බාහිර"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"නොදනී"</string>
 </resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index c49ffc5..f7606ec 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Hovor kontaktu <xliff:g id="CALLER">%s</xliff:g> bol zrušený, aby mohlo prebehnúť tiesňové volanie."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Váš hovor bol zrušený, aby mohlo prebehnúť tiesňové volanie."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Hovor na pozadí"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"Aplikácia <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> umiestnila hovor do pozadia. Táto aplikácia môže mať počas hovoru prístup k zvuku a prehrávať ho."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> spracúva hovor na pozadí. Táto aplikácia môže mať počas hovoru prístup k zvuku a prehrávať ho."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"Aplikácia <xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> prestala reagovať"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Na hovor bola použitá telefónna aplikácia, ktorá bola vopred nainštalovaná vo vašom zariadení"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Zvuk hovoru bol vypnutý."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Ponuka pre vývojárov Telecomu"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Počas tiesňového volania nie je možné prijímať hovory."</string>
     <string name="cancel" msgid="6733466216239934756">"Zrušiť"</string>
+    <string name="back" msgid="6915955601805550206">"Späť"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Slúchadlo"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Káblové slúchadlo s mikrofónom"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Reproduktor"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Externé"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Neznáme"</string>
 </resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 5bc9d83..138524b 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Klic za stik <xliff:g id="CALLER">%s</xliff:g> je prekinjen zaradi vzpostavljanja klica v sili."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Klic je prekinjen zaradi vzpostavljanja klica v sili."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Klic v ozadju"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"Aplikacija <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> je klic premaknila v ozadje. Ta aplikacija morda dostopa do zvoka in ga predvaja prek klica."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> obdeluje klic v ozadju. Ta aplikacija morda dostopa do zvoka in ga predvaja prek klica."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"Aplikacija <xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> se ne odziva več"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Klic je bil opravljen prek aplikacije za klicanje, ki jo je v napravo namestil proizvajalec"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Klic izključen."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Meni za razvijalce Telecom"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Med klicem v sili ni mogoče sprejeti klicev."</string>
     <string name="cancel" msgid="6733466216239934756">"Prekliči"</string>
+    <string name="back" msgid="6915955601805550206">"Nazaj"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Slušalka"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Žične slušalke z mikrofonom"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Zvočnik"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Zunanje"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Neznano"</string>
 </resources>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index db3eda3..0a36a40 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Telefonata me <xliff:g id="CALLER">%s</xliff:g> është shkëputur sepse është kryer një telefonatë urgjence."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Telefonata jote është shkëputur sepse është kryer një telefonatë urgjence."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Telefonatë në sfond"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> e ka vendosur një telefonatë në sfond. Ky aplikacion mund të ketë qasje dhe të luajë audio mbi telefonatë."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> po përpunon një telefonatë në sfond. Ky aplikacion mund të ketë qasje dhe të luajë audio mbi telefonatë."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> nuk përgjigjet më"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Telefonata jote ka përdorur aplikacionin e telefonit që ke marrë me pajisjen tënde"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Telefonata kaloi në heshtje."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Menyja e zhvilluesit të telekomunikimit"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Nuk mund të marrësh telefonata kur je në një telefonatë urgjence."</string>
     <string name="cancel" msgid="6733466216239934756">"Anulo"</string>
+    <string name="back" msgid="6915955601805550206">"Pas"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Receptori"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Kufje me tel"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Altoparlant"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"E jashtme"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"E panjohur"</string>
 </resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 36bf352..b846841 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Позив са <xliff:g id="CALLER">%s</xliff:g> је прекинут јер се упућује хитни позив."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Позив је прекинут јер се упућује хитни позив."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Позив у позадини"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"Апликација <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> је упутила позив у позадини. Она може да приступа звуку и пушта га током позива."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"Апликација <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> обрађује позив у позадини. Она може да приступа звуку и да га пушта током позива."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> више не реагује"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Позив је користио апликацију за телефонирање коју сте добили уз уређај"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Звук позива је искључен."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Мени за програмере Telecom-а"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"За време хитног позива није могуће преузимати друге позиве."</string>
     <string name="cancel" msgid="6733466216239934756">"Откажи"</string>
+    <string name="back" msgid="6915955601805550206">"Назад"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Слушалица"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Жичане слушалице"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Звучник"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Екстерни"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Непознато"</string>
 </resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index 34a3b6d..acc6dc6 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Samtalet <xliff:g id="CALLER">%s</xliff:g> kopplades från eftersom ett nödsamtal ringdes."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Samtalets kopplades bort på grund av ett nödsamtal."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Bakgrundssamtal"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> har ringt ett samtal i bakgrunden. Denna app kan få åtkomst till och spela upp ljud från samtalet."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> bearbetar ett samtal i bakgrunden. Den här appen kan få åtkomst till och spela upp ljud från samtalet."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> slutade svara"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Samtalet använde telefonappen som medföljer enheten"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Samtalets ljud avstängt."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Meny för telekomutvecklare"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Det går inte att besvara samtal medan ett nödsamtal pågår."</string>
     <string name="cancel" msgid="6733466216239934756">"Avbryt"</string>
+    <string name="back" msgid="6915955601805550206">"Tillbaka"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Lur"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Kabelanslutet headset"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Högtalare"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Extern"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Okänd"</string>
 </resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 1eb5293..84f7294 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Simu uliyompigia <xliff:g id="CALLER">%s</xliff:g> imekatwa kwa sababu kuna simu ya dharura inayopigwa."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Simu yako imekatwa kwa sababu kuna simu ya dharura inayopigwa."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Simu ya chinichini"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> inapiga simu chinichini. Huenda programu hii inafikia na kucheza sauti huku simu ikiendelea."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> inachakata simu fulani chinichini. Huenda programu hii inafikia na kucheza sauti huku simu ikiendelea."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> imeacha kufanya kazi"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Ulipiga simu kwa kutumia Programu ya simu iliyokuja na kifaa chako"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Simu imezimwa."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Menyu ya Msanidi programu wa Telecom"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Huwezi kupokea simu nyingine wakati unashiriki katika simu ya dharura."</string>
     <string name="cancel" msgid="6733466216239934756">"Ghairi"</string>
+    <string name="back" msgid="6915955601805550206">"Rudi nyuma"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Spika ya sikioni"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Vifaa vya sauti vyenye waya"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Spika"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Ya nje"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Haijulikani"</string>
 </resources>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 90a33a7..18b5861 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"அவசர அழைப்பு மேற்கொள்ளப்பட்டதால் <xliff:g id="CALLER">%s</xliff:g> உடனான அழைப்பு துண்டிக்கப்பட்டது."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"அவசர அழைப்பு மேற்கொள்ளப்படுவதால் உங்கள் அழைப்பு துண்டிக்கப்பட்டுள்ளது."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"பின்னணி அழைப்பு"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"அழைப்பை பின்னணியில் செயல்படும் வகையில் <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> மாற்றியுள்ளது. அழைப்பின் மூலமாக இந்த ஆப்ஸ் ஆடியோவை அணுகி இயக்கக்கூடும்."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> அழைப்பைப் பின்னணியில் செயலாக்குகிறது. அழைப்பின்போதே இந்த ஆப்ஸ் ஆடியோவை அணுகி அதைச் செயலாக்கக்கூடும்."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> செயலிழந்துவிட்டது"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"உங்கள் சாதனத்துடன் கிடைக்கும் மொபைல் ஆப்ஸ் மூலம் அழைப்பு மேற்கொள்ளப்பட்டது"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"அழைப்பு முடக்கப்பட்டது."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"டெலிகாம் டெவெலப்பர் மெனு"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"அவசர அழைப்பின்போது அழைப்புகளை ஏற்க முடியாது."</string>
     <string name="cancel" msgid="6733466216239934756">"ரத்துசெய்"</string>
+    <string name="back" msgid="6915955601805550206">"பின்செல்"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"ஒலி கேட்கும் பகுதி"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"புளூடூத்"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"வயர் ஹெட்செட்"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"ஸ்பீக்கர்"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"வெளிப்புறச் சாதனம்"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"தெரியவில்லை"</string>
 </resources>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index b904115..5ed2ebe 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"అత్యవసర కాల్ చేయబడినందున <xliff:g id="CALLER">%s</xliff:g>తో కాల్ డిస్‌కనెక్ట్ చేయబడింది."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"అత్యవసర కాల్ చేయబడినందున మీ కాల్ డిస్‌కనెక్ట్ చేయబడింది."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"నేపథ్యం కాల్"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> కాల్‌ను నేపథ్యంలోకి పంపింది. కాల్ ద్వారా ఈ యాప్, ఆడియోను యాక్సెస్ ఇంకా ప్లే చేస్తుండవచ్చు."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> బ్యాక్‌గ్రౌండ్‌లో కాల్‌ను ప్రాసెస్ చేస్తోంది. ఈ యాప్ కాల్ ద్వారా ఆడియోను యాక్సెస్ చేసి ప్లే చేస్తుండవచ్చు."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> స్పందించడం ఆగిపోయింది"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"మీ కాల్, మీ పరికరంతో వచ్చిన ఫోన్ యాప్‌ను ఉపయోగించింది"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"కాల్ మ్యూట్ చేయబడింది."</string>
@@ -51,17 +51,17 @@
     <string name="outgoing_call_error_no_phone_number_supplied" msgid="7665135102566099778">"కాల్ చేయడానికి, చెల్లుబాటు అయ్యే నంబర్‌ను నమోదు చేయండి."</string>
     <string name="duplicate_video_call_not_allowed" msgid="5754746140185781159">"ఈ సమయంలో కాల్‌ను జోడించడం సాధ్యపడదు."</string>
     <string name="no_vm_number" msgid="2179959110602180844">"వాయిస్ మెయిల్ నంబర్ లేదు"</string>
-    <string name="no_vm_number_msg" msgid="1339245731058529388">"సిమ్ కార్డులో వాయిస్ మెయిల్ నంబర్ ఏదీ నిల్వ చేయబడలేదు."</string>
-    <string name="add_vm_number_str" msgid="5179510133063168998">"నంబర్‌ను జోడించు"</string>
+    <string name="no_vm_number_msg" msgid="1339245731058529388">"సిమ్ కార్డులో వాయిస్ మెయిల్ నంబర్ ఏదీ స్టోరేజ్‌ చేయబడలేదు."</string>
+    <string name="add_vm_number_str" msgid="5179510133063168998">"నంబర్‌ను జోడించండి"</string>
     <string name="change_default_dialer_dialog_title" msgid="5861469279421508060">"<xliff:g id="NEW_APP">%s</xliff:g>ను మీ ఆటోమేటిక్ ఫోన్ యాప్‌గా చేయాలా?"</string>
     <string name="change_default_dialer_dialog_affirmative" msgid="8604665314757739550">"ఆటోమేటిక్‌గా సెట్ చేయండి"</string>
-    <string name="change_default_dialer_dialog_negative" msgid="8648669840052697821">"రద్దు చేయి"</string>
+    <string name="change_default_dialer_dialog_negative" msgid="8648669840052697821">"రద్దు చేయండి"</string>
     <string name="change_default_dialer_warning_message" msgid="8461963987376916114">"<xliff:g id="NEW_APP">%s</xliff:g> అన్ని రకాల కాల్స్‌ను చేయగలదు, సంబంధిత అన్ని అంశాలను కంట్రోల్ చేయగలదు. మీరు విశ్వసించే యాప్‌లను మాత్రమే ఆటోమేటిక్ ఫోన్ యాప్‌గా సెట్ చేయాలి."</string>
     <string name="change_default_call_screening_dialog_title" msgid="5365787219927262408">"<xliff:g id="NEW_APP">%s</xliff:g>ను మీ ఆటోమేటిక్ కాల్ స్క్రీనింగ్ యాప్‌గా సెట్ చేయాలా?"</string>
     <string name="change_default_call_screening_warning_message_for_disable_old_app" msgid="2039830033533243164">"<xliff:g id="OLD_APP">%s</xliff:g> ఇకపై స్క్రీన్ కాల్స్‌ను చేయలేదు."</string>
     <string name="change_default_call_screening_warning_message" msgid="9020537562292754269">"<xliff:g id="NEW_APP">%s</xliff:g> మీ కాంటాక్టుల్లో లేని కాలర్ల సమాచారాన్ని చూడగలుగుతుంది, అలాగే ఈ కాల్స్‌ను బ్లాక్ చేయగలుగుతుంది. మీరు విశ్వసించే యాప్‌లను మాత్రమే ఆటోమేటిక్ కాల్ స్క్రీనింగ్‌ యాప్‌గా సెట్ చేయాలి."</string>
     <string name="change_default_call_screening_dialog_affirmative" msgid="7162433828280058647">"ఆటోమేటిక్‌గా సెట్ చేయండి"</string>
-    <string name="change_default_call_screening_dialog_negative" msgid="1839266125623106342">"రద్దు చేయి"</string>
+    <string name="change_default_call_screening_dialog_negative" msgid="1839266125623106342">"రద్దు చేయండి"</string>
     <string name="blocked_numbers" msgid="8322134197039865180">"బ్లాక్ చేయబడిన నంబర్‌లు"</string>
     <string name="blocked_numbers_msg" msgid="2797422132329662697">"మీరు బ్లాక్ చేయబడిన నంబర్‌ల నుండి కాల్స్‌ లేదా వచన మెసేజ్‌లను స్వీకరించరు."</string>
     <string name="block_number" msgid="3784343046852802722">"నంబర్‌ను జోడించండి"</string>
@@ -69,12 +69,12 @@
     <string name="unblock_button" msgid="8732021675729981781">"అన్‌బ్లాక్ చేయి"</string>
     <string name="add_blocked_dialog_body" msgid="8599974422407139255">"దీని నుండి కాల్స్‌ మరియు మెసేజ్‌లను బ్లాక్ చేయండి"</string>
     <string name="add_blocked_number_hint" msgid="8769422085658041097">"ఫోన్ నంబర్"</string>
-    <string name="block_button" msgid="485080149164258770">"బ్లాక్ చేయి"</string>
-    <string name="non_primary_user" msgid="315564589279622098">"కేవలం పరికర యజమాని మాత్రమే బ్లాక్ చేసిన నంబర్‌లను వీక్షించగలరు మరియు నిర్వహించగలరు."</string>
+    <string name="block_button" msgid="485080149164258770">"బ్లాక్ చేయండి"</string>
+    <string name="non_primary_user" msgid="315564589279622098">"కేవలం పరికర యజమాని మాత్రమే బ్లాక్ చేసిన నంబర్‌లను చూడగలరు మరియు నిర్వహించగలరు."</string>
     <string name="delete_icon_description" msgid="5335959254954774373">"అన్‌బ్లాక్ చేస్తుంది"</string>
     <string name="blocked_numbers_butter_bar_title" msgid="582982373755950791">"బ్లాకింగ్ తాత్కాలికంగా ఆఫ్ చేయబడింది"</string>
     <string name="blocked_numbers_butter_bar_body" msgid="1261213114919301485">"మీరు అత్యవసర నంబర్‌కి డయల్ చేసాక లేదా వచన మెసేజ్‌ పంపాక, అత్యవసర సేవలు తిరిగి మిమ్మల్ని సంప్రదించగలిగేలా చేయడానికి బ్లాకింగ్ ఆఫ్ చేయబడుతుంది."</string>
-    <string name="blocked_numbers_butter_bar_button" msgid="2704456308072489793">"ఇప్పుడే మళ్లీ ప్రారంభించు"</string>
+    <string name="blocked_numbers_butter_bar_button" msgid="2704456308072489793">"ఇప్పుడే మళ్లీ ప్రారంభించండి"</string>
     <string name="blocked_numbers_number_blocked_message" msgid="4314736791180919167">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> బ్లాక్ చేయబడింది"</string>
     <string name="blocked_numbers_number_unblocked_message" msgid="2933071624674945601">"<xliff:g id="UNBLOCKED_NUMBER">%1$s</xliff:g> అన్‌బ్లాక్ చేయబడింది"</string>
     <string name="blocked_numbers_block_emergency_number_message" msgid="4198550501500893890">"అత్యవసర నంబర్‌ను బ్లాక్ చేయడం సాధ్యపడలేదు."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"టెలికామ్ డెవలపర్ మెనూ"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"అత్యవసర కాల్‌లో వున్నప్పుడు కాల్స్‌ను స్వీకరించడానికి వీలుపడదు."</string>
     <string name="cancel" msgid="6733466216239934756">"రద్దు చేయండి"</string>
+    <string name="back" msgid="6915955601805550206">"వెనుకకు"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"ఇయర్‌పీస్"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"బ్లూటూత్"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"వైర్ ఉన్న హెడ్‌సెట్"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"స్పీకర్"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"వెలుపలి"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"తెలియదు"</string>
 </resources>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 4f1c825..678af2d 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"สายที่โทรหา <xliff:g id="CALLER">%s</xliff:g> ถูกตัดเนื่องจากมีการโทรหาหมายเลขฉุกเฉิน"</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"สายของคุณถูกตัดเพราะมีการโทรหาหมายเลขฉุกเฉิน"</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"การโทรในเบื้องหลัง"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> ได้ทำการโทรในเบื้องหลัง แอปนี้อาจกำลังเข้าถึงและเล่นเสียงผ่านการโทร"</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> กำลังประมวลผลการโทรในเบื้องหลัง โดยแอปนี้อาจเข้าถึงและเล่นเสียงผ่านการโทร"</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> หยุดตอบสนอง"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"สายของคุณใช้แอปโทรศัพท์ที่มาพร้อมกับอุปกรณ์"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"ปิดเสียงการโทร"</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"เมนูนักพัฒนาโทรคมนาคม"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"การโทรนั้นจะทำขณะอยู่ในการโทรฉุกเฉินไม่ได้"</string>
     <string name="cancel" msgid="6733466216239934756">"ยกเลิก"</string>
+    <string name="back" msgid="6915955601805550206">"กลับ"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"หูฟังโทรศัพท์"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"บลูทูธ"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"ชุดหูฟังแบบมีสาย"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"ลำโพง"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"ภายนอก"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"ไม่ทราบ"</string>
 </resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 85c1b3f..495c191 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Nadiskonekta ang tawag kay <xliff:g id="CALLER">%s</xliff:g> dahil sa ginagawang pang-emergency na tawag."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Nadiskonekta ang iyong tawag dahil sa ginagawang pang-emergency na tawag."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Tawag sa background"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"Naglagay ng tawag ang <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> sa background. Posibleng ina-access at pine-play ng app na ito ang audio sa tawag."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"Pinoproseso ng <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> ang isang tawag sa background. Posibleng ina-access at pine-play ng app na ito ang audio sa tawag."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"Huminto ang <xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> sa pagtugon"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Ginamit ng tawag mo ang app na telepono na kasama sa iyong device"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Naka-mute ang tawag."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Menu ng Telecom Developer"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Hindi puwedeng sumagot ng mga tawag habang nasa emergency na tawag."</string>
     <string name="cancel" msgid="6733466216239934756">"Kanselahin"</string>
+    <string name="back" msgid="6915955601805550206">"Bumalik"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Earpiece"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Wired na headset"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Speaker"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"External"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Hindi Alam"</string>
 </resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 66da8ab..1309682 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Bir acil durum araması yapıldığı için <xliff:g id="CALLER">%s</xliff:g> ile olan görüşmenin bağlantısı kesildi."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Bir acil durum araması yapıldığı için görüşmenizin bağlantısı kesildi."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Arka plandaki arama"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> arka plana bir arama yerleştirdi. Bu uygulama arama üzerinden sese erişiyor ve ses çalıyor olabilir."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g>, bir aramayı arka planda işliyor. Bu uygulama, arama üzerinden sese erişiyor ve bu sesi çalıyor olabilir."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> yanıt vermeyi durdurdu"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Görüşmeniz için, cihazınızla gelen telefon uygulaması kullanıldı"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Çağrı sesi kapatıldı."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Telekomünikasyon Geliştirici Menüsü"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Acil durum araması sırasında arama alınamaz."</string>
     <string name="cancel" msgid="6733466216239934756">"İptal"</string>
+    <string name="back" msgid="6915955601805550206">"Geri"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Kulaklık"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Kablolu mikrofonlu kulaklık"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Hoparlör"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Harici"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Bilinmiyor"</string>
 </resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 6eddc79..7b81d25 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Виклик абонента <xliff:g id="CALLER">%s</xliff:g> припинено, оскільки здійснюється екстрений виклик."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Виклик перервано, оскільки здійснюється екстрений виклик."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"У фоновий режим"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"Додаток <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> перевів виклик у фоновий режим і може відтворювати аудіо під час виклику."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"Додаток <xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> обробляє виклик у фоновому режимі. Він може отримувати доступ до аудіо виклику та відтворювати під час нього своє аудіо."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"Додаток <xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> не відповідає"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Поточний дзвінок було перенаправлено в додаток, що постачається разом із пристроєм"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Звук виклику вимкнено."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Меню розробника Telecom"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Під час екстрених викликів не можна приймати інші."</string>
     <string name="cancel" msgid="6733466216239934756">"Скасувати"</string>
+    <string name="back" msgid="6915955601805550206">"Назад"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Динамік"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Дротова гарнітура"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Гучний зв’язок"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Зовнішні джерела"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Невідомо"</string>
 </resources>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index c42f5d9..afd8d0a 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"ہنگامی کال کی وجہ سے <xliff:g id="CALLER">%s</xliff:g> کی کال کو غیر منسلک کر دیا گیا ہے۔"</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"ہنگامی کال لگائے جانے کی وجہ سے آپ کی کال غیر منسلک ہوگئی ہے۔"</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"پس منظر کی کال"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> نے پس منظر میں کال لگا دیا ہے۔ یہ ایپ کال کے دوران آواز تک رسائی حاصل اور چلا سکتی ہے۔"</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> پس منظر میں کال پر کارروائی کر رہی ہے۔ یہ ایپ کال کے دوران آواز تک رسائی حاصل اور چلا سکتی ہے۔"</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> نے جواب دینا بند کر دیا"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"آپ کی کال نے آپ کے آلہ کے ساتھ آئی ہوئی فون ایپ کا استعمال کیا"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"کال خاموش کر دی گئی۔"</string>
@@ -120,7 +120,14 @@
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="1713632946174016619">"کال مسدود کرنا غیر فعال ہو گیا ہے"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"ہنگامی کال کی گئی"</string>
     <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"ہنگامی حالت میں جواب دہندگان کو آپ سے رابطہ کرنے کی اجازت دینے کیلئے کال مسدود کرنا غیر فعال ہو گیا ہے۔"</string>
-    <string name="developer_title" msgid="9146088855661672353">"ٹیلی کام ڈویلپر مینو"</string>
+    <string name="developer_title" msgid="9146088855661672353">"ٹیلی کام ڈویلپر مینیو"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"ہنگامی کال کے دوران کالز نہیں لی جائیں گی۔"</string>
     <string name="cancel" msgid="6733466216239934756">"منسوخ کریں"</string>
+    <string name="back" msgid="6915955601805550206">"پیچھے"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"ایئر پیس"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"بلوٹوتھ"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"تار والا ہیڈسیٹ"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"اسپیکر"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"خارجی"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"نامعلوم"</string>
 </resources>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index c3ae499..688b6a7 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Favqulodda chaqiruv amalga oshirilayotgani uchun <xliff:g id="CALLER">%s</xliff:g> bilan suhbatingiz tugatildi."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Favqulodda chaqiruv amalga oshirilayotgani uchun joriy chaqiruvingiz to‘xtatildi."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Orqa fondagi chaqiruv"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> ilovasi chaqiruvni orqa fonga joyladi. Bu ilova ovozli chaqiruvga kirishi yoki unda audio ijro etishi mumkin."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> chaqiruvni fonda qayta ishlamoqda. Bu ilova ovozli chaqiruvga kirishi yoki unda audio ijro etishi mumkin."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> javob bermayapti"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Chaqiruv qurilmangizga avvaldan o‘rnatilgan ilova orqali amalga oshirildi"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Qo‘ng‘iroq ovozi o‘chirildi."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Telecom dasturchisi menyusi"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Favqulodda chaqiruv vaqtida boshqa chaqiruvlarni qabul qilish imkonsiz."</string>
     <string name="cancel" msgid="6733466216239934756">"Bekor qilish"</string>
+    <string name="back" msgid="6915955601805550206">"Orqaga"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Quloq karnaychasi"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Simli garnitura"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Karnay"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Tashqi"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Noaniq"</string>
 </resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index dfe6b7b..0920d3b 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Cuộc gọi đến <xliff:g id="CALLER">%s</xliff:g> đã bị ngắt kết nối do một cuộc gọi khẩn cấp được thực hiện."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Cuộc gọi của bạn đã bị ngắt kết nối do một cuộc gọi khẩn cấp đang được thực hiện."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Cuộc gọi trong nền"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> đã gọi điện ở chế độ nền. Ứng dụng này có thể đang truy cập và phát âm thanh qua cuộc gọi."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> đang xử lý cuộc gọi ở chế độ nền. Ứng dụng này có thể đang truy cập và phát âm thanh qua cuộc gọi."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> đã dừng phản hồi"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Cuộc gọi của bạn đã dùng ứng dụng dành cho điện thoại đi kèm với thiết bị"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Đã tắt tiếng cuộc gọi."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Menu nhà phát triển dịch vụ viễn thông"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Bạn không thể gọi điện trong khi thực hiện cuộc gọi khẩn cấp."</string>
     <string name="cancel" msgid="6733466216239934756">"Hủy"</string>
+    <string name="back" msgid="6915955601805550206">"Quay lại"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Loa tai nghe"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"Tai nghe có dây"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Loa"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Bên ngoài"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Không xác định"</string>
 </resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index d91c260..b926e55 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"由于要进行紧急呼叫,与 <xliff:g id="CALLER">%s</xliff:g> 的通话已中断。"</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"由于要进行紧急呼叫,您的通话已中断。"</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"后台通话"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> 已将通话切换到后台进行。此应用可以接入通话,并播放通话音频。"</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"“<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g>”正在后台处理通话。此应用可在通话期间获取和播放音频。"</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g>已停止响应"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"系统使用您设备自带的电话应用拨打了电话"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"通话已静音。"</string>
@@ -89,7 +89,7 @@
     <string name="answering_ends_other_managed_calls" msgid="3974069768615307659">"如果接听此来电,您当前的通话会中断。"</string>
     <string name="answering_ends_other_managed_video_call" msgid="1988508241432031327">"如果接听此来电,您当前的视频通话会中断。"</string>
     <string name="answer_incoming_call" msgid="2045888814782215326">"接听"</string>
-    <string name="decline_incoming_call" msgid="922147089348451310">"拒绝"</string>
+    <string name="decline_incoming_call" msgid="922147089348451310">"拒接"</string>
     <string name="cant_call_due_to_no_supported_service" msgid="1635626384149947077">"无法拨出电话,因为没有通话帐号支持拨打这类电话。"</string>
     <string name="cant_call_due_to_ongoing_call" msgid="8004235328451385493">"由于当前正在进行 <xliff:g id="OTHER_CALL">%1$s</xliff:g> 通话,因此无法拨打电话。"</string>
     <string name="cant_call_due_to_ongoing_calls" msgid="6379163795277824868">"由于当前正在进行 <xliff:g id="OTHER_CALL">%1$s</xliff:g> 通话,因此无法拨打电话。"</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"电信开发者菜单"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"紧急呼叫时无法接听来电。"</string>
     <string name="cancel" msgid="6733466216239934756">"取消"</string>
+    <string name="back" msgid="6915955601805550206">"返回"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"手机听筒"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"蓝牙"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"有线耳机"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"免提"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"外部"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"未知"</string>
 </resources>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index df1a850..e7b1a07 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"因撥打緊急電話緣故,與<xliff:g id="CALLER">%s</xliff:g>的通話已中斷。"</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"因撥打緊急電話緣故,您的通話已中斷。"</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"背景通話"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"「<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g>」已將通話放到背景。這個應用程式可以存取該通話,並透過該通話播放音訊。"</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"「<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g>」正在處理背景中的通話。這個應用程式或會存取通話,或是在通話中播放音訊。"</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g>已停止回應"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"您使用了裝置隨付的手機應用程式來通話"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"通話已靜音。"</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"電信開發商選單"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"使用緊急電話期間無法接聽電話。"</string>
     <string name="cancel" msgid="6733466216239934756">"取消"</string>
+    <string name="back" msgid="6915955601805550206">"返回"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"聽筒"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"藍牙"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"有線耳機"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"喇叭"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"外部"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"不明"</string>
 </resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 39359b3..21b8ae9 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"由於你正在撥打緊急電話,因此你與<xliff:g id="CALLER">%s</xliff:g>的通話已中斷。"</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"你撥出了緊急電話,因此目前的通話已中斷。"</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"背景通話"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"「<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g>」已將通話切換到在背景進行。這個應用程式可能會在通話期間存取及播放音訊。"</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"「<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g>」正在背景處理通話,因此可能會在通話期間存取及播放音訊。"</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"「<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g>」已停止回應"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"目前是透過裝置內建的電話應用程式進行通話"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"通話已靜音。"</string>
@@ -39,7 +39,7 @@
     <string name="respond_via_sms_canned_response_2" msgid="2052951316129952406">"我待會就回電。"</string>
     <string name="respond_via_sms_canned_response_3" msgid="6656147963478092035">"我晚點回電。"</string>
     <string name="respond_via_sms_canned_response_4" msgid="9141132488345561047">"我現在不方便講話,晚點再打來好嗎?"</string>
-    <string name="respond_via_sms_setting_title" msgid="4762275482898830160">"快速回應"</string>
+    <string name="respond_via_sms_setting_title" msgid="4762275482898830160">"應答短訊"</string>
     <string name="respond_via_sms_setting_title_2" msgid="4914853536609553457">"編輯快速回應"</string>
     <string name="respond_via_sms_setting_summary" msgid="8054571501085436868"></string>
     <string name="respond_via_sms_edittext_dialog_title" msgid="6579353156073272157">"快速回應"</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"電信開發人員選單"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"如果裝置已撥打緊急電話,就無法進行其他通話。"</string>
     <string name="cancel" msgid="6733466216239934756">"取消"</string>
+    <string name="back" msgid="6915955601805550206">"返回"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"耳機"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"藍牙"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"有線耳機"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"喇叭"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"外部"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"不明"</string>
 </resources>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index a98e4f0..fbde58b 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -30,7 +30,7 @@
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"Ikholi eya ku-<xliff:g id="CALLER">%s</xliff:g> inqanyuliwe ngenxa yekholi yesimo esiphuthumayo efakwayo."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"Ikholi yakho inqanyuliwe ngenxa yekholi yesimo esiphuthumayo eyenziwe."</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"Ikholi engemuva"</string>
-    <string name="notification_audioProcessing_body" msgid="6397005913770420388">"<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> yenze ikholi ngemuva. Kungenzeka ukuthi lolu hlelo lokusebenza lufinyelela futhi ludlala okulalelwayo ngaphezu kwekholi."</string>
+    <string name="notification_audioProcessing_body" msgid="8811420157964118913">"I-<xliff:g id="AUDIO_PROCESSING_APP_NAME">%s</xliff:g> icubungula ikholi ngemuva. Kungenzeka ukuthi le app ifinyelela futhi idlala okulalelwayo ngaphezu kwekholi."</string>
     <string name="notification_incallservice_not_responding_title" msgid="5347557574288598548">"<xliff:g id="IN_CALL_SERVICE_APP_NAME">%s</xliff:g> iyeke ukuphendula"</string>
     <string name="notification_incallservice_not_responding_body" msgid="9209308270131968623">"Ikholi yakho isebenzise uhlelo lokusebenza lefoni elize nedivayisi yakho"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Ikholu ithulisiwe"</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Imenyu yonjiniyela we-Telecom"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Amakholi awakwazi ukuthathwa ngesikhathi ukukholi yesimo esiphuthumayo."</string>
     <string name="cancel" msgid="6733466216239934756">"Khansela"</string>
+    <string name="back" msgid="6915955601805550206">"Emuva"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Isipikha sendlebe"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"I-Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"I-headset enentambo"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Isipikha"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Okungaphandle"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Akwaziwa"</string>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index bf5abca..d67df4b 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -381,4 +381,18 @@
     <string name="developer_enhanced_call_blocking" translatable="false">Enhanced Call Blocking</string>
     <!-- Button label for generic cancel action [CHAR LIMIT=20] -->
     <string name="cancel">Cancel</string>
+    <!-- Button label for generic back action [CHAR LIMIT=20] -->
+    <string name="back">Back</string>
+    <!-- The user-visible name of the earpiece type CallEndpoint -->
+    <string name="callendpoint_name_earpiece">Earpiece</string>
+    <!-- The user-visible name of the bluetooth type CallEndpoint -->
+    <string name="callendpoint_name_bluetooth">Bluetooth</string>
+    <!-- The user-visible name of the wired headset type CallEndpoint -->
+    <string name="callendpoint_name_wiredheadset">Wired headset</string>
+    <!-- The user-visible name of the speaker type CallEndpoint -->
+    <string name="callendpoint_name_speaker">Speaker</string>
+    <!-- The user-visible name of the streaming type CallEndpoint -->
+    <string name="callendpoint_name_streaming">External</string>
+    <!-- The user-visible name of the unknown new type CallEndpoint -->
+    <string name="callendpoint_name_unknown">Unknown</string>
 </resources>
diff --git a/src/com/android/server/telecom/Analytics.java b/src/com/android/server/telecom/Analytics.java
index 145e9b4..bbcf858 100644
--- a/src/com/android/server/telecom/Analytics.java
+++ b/src/com/android/server/telecom/Analytics.java
@@ -72,96 +72,101 @@
     private static final String CLEAR_ANALYTICS_ARG = "clear";
 
     public static final Map<String, Integer> sLogEventToAnalyticsEvent = Map.ofEntries(
-                entry(LogUtils.Events.SET_SELECT_PHONE_ACCOUNT,
-                        AnalyticsEvent.SET_SELECT_PHONE_ACCOUNT),
-                entry(LogUtils.Events.REQUEST_HOLD, AnalyticsEvent.REQUEST_HOLD),
-                entry(LogUtils.Events.REQUEST_UNHOLD, AnalyticsEvent.REQUEST_UNHOLD),
-                entry(LogUtils.Events.SWAP, AnalyticsEvent.SWAP),
-                entry(LogUtils.Events.SKIP_RINGING, AnalyticsEvent.SKIP_RINGING),
-                entry(LogUtils.Events.CONFERENCE_WITH, AnalyticsEvent.CONFERENCE_WITH),
-                entry(LogUtils.Events.SPLIT_FROM_CONFERENCE, AnalyticsEvent.SPLIT_CONFERENCE),
-                entry(LogUtils.Events.SET_PARENT, AnalyticsEvent.SET_PARENT),
-                entry(LogUtils.Events.MUTE, AnalyticsEvent.MUTE),
-                entry(LogUtils.Events.UNMUTE, AnalyticsEvent.UNMUTE),
-                entry(LogUtils.Events.AUDIO_ROUTE_BT, AnalyticsEvent.AUDIO_ROUTE_BT),
-                entry(LogUtils.Events.AUDIO_ROUTE_EARPIECE, AnalyticsEvent.AUDIO_ROUTE_EARPIECE),
-                entry(LogUtils.Events.AUDIO_ROUTE_HEADSET, AnalyticsEvent.AUDIO_ROUTE_HEADSET),
-                entry(LogUtils.Events.AUDIO_ROUTE_SPEAKER, AnalyticsEvent.AUDIO_ROUTE_SPEAKER),
-                entry(LogUtils.Events.SILENCE, AnalyticsEvent.SILENCE),
-                entry(LogUtils.Events.SCREENING_COMPLETED, AnalyticsEvent.SCREENING_COMPLETED),
-                entry(LogUtils.Events.BLOCK_CHECK_FINISHED, AnalyticsEvent.BLOCK_CHECK_FINISHED),
-                entry(LogUtils.Events.DIRECT_TO_VM_FINISHED, AnalyticsEvent.DIRECT_TO_VM_FINISHED),
-                entry(LogUtils.Events.REMOTELY_HELD, AnalyticsEvent.REMOTELY_HELD),
-                entry(LogUtils.Events.REMOTELY_UNHELD, AnalyticsEvent.REMOTELY_UNHELD),
-                entry(LogUtils.Events.REQUEST_PULL, AnalyticsEvent.REQUEST_PULL),
-                entry(LogUtils.Events.REQUEST_ACCEPT, AnalyticsEvent.REQUEST_ACCEPT),
-                entry(LogUtils.Events.REQUEST_REJECT, AnalyticsEvent.REQUEST_REJECT),
-                entry(LogUtils.Events.SET_ACTIVE, AnalyticsEvent.SET_ACTIVE),
-                entry(LogUtils.Events.SET_DISCONNECTED, AnalyticsEvent.SET_DISCONNECTED),
-                entry(LogUtils.Events.SET_HOLD, AnalyticsEvent.SET_HOLD),
-                entry(LogUtils.Events.SET_DIALING, AnalyticsEvent.SET_DIALING),
-                entry(LogUtils.Events.START_CONNECTION, AnalyticsEvent.START_CONNECTION),
-                entry(LogUtils.Events.BIND_CS, AnalyticsEvent.BIND_CS),
-                entry(LogUtils.Events.CS_BOUND, AnalyticsEvent.CS_BOUND),
-                entry(LogUtils.Events.SCREENING_SENT, AnalyticsEvent.SCREENING_SENT),
-                entry(LogUtils.Events.DIRECT_TO_VM_INITIATED,
-                        AnalyticsEvent.DIRECT_TO_VM_INITIATED),
-                entry(LogUtils.Events.BLOCK_CHECK_INITIATED, AnalyticsEvent.BLOCK_CHECK_INITIATED),
-                entry(LogUtils.Events.FILTERING_INITIATED, AnalyticsEvent.FILTERING_INITIATED),
-                entry(LogUtils.Events.FILTERING_COMPLETED, AnalyticsEvent.FILTERING_COMPLETED),
-                entry(LogUtils.Events.FILTERING_TIMED_OUT, AnalyticsEvent.FILTERING_TIMED_OUT));
+            entry(LogUtils.Events.SET_SELECT_PHONE_ACCOUNT,
+                    AnalyticsEvent.SET_SELECT_PHONE_ACCOUNT),
+            entry(LogUtils.Events.REQUEST_HOLD, AnalyticsEvent.REQUEST_HOLD),
+            entry(LogUtils.Events.REQUEST_UNHOLD, AnalyticsEvent.REQUEST_UNHOLD),
+            entry(LogUtils.Events.SWAP, AnalyticsEvent.SWAP),
+            entry(LogUtils.Events.SKIP_RINGING, AnalyticsEvent.SKIP_RINGING),
+            entry(LogUtils.Events.CONFERENCE_WITH, AnalyticsEvent.CONFERENCE_WITH),
+            entry(LogUtils.Events.SPLIT_FROM_CONFERENCE, AnalyticsEvent.SPLIT_CONFERENCE),
+            entry(LogUtils.Events.SET_PARENT, AnalyticsEvent.SET_PARENT),
+            entry(LogUtils.Events.MUTE, AnalyticsEvent.MUTE),
+            entry(LogUtils.Events.UNMUTE, AnalyticsEvent.UNMUTE),
+            entry(LogUtils.Events.AUDIO_ROUTE_BT, AnalyticsEvent.AUDIO_ROUTE_BT),
+            entry(LogUtils.Events.AUDIO_ROUTE_EARPIECE, AnalyticsEvent.AUDIO_ROUTE_EARPIECE),
+            entry(LogUtils.Events.AUDIO_ROUTE_HEADSET, AnalyticsEvent.AUDIO_ROUTE_HEADSET),
+            entry(LogUtils.Events.AUDIO_ROUTE_SPEAKER, AnalyticsEvent.AUDIO_ROUTE_SPEAKER),
+            entry(LogUtils.Events.SILENCE, AnalyticsEvent.SILENCE),
+            entry(LogUtils.Events.SCREENING_COMPLETED, AnalyticsEvent.SCREENING_COMPLETED),
+            entry(LogUtils.Events.BLOCK_CHECK_FINISHED, AnalyticsEvent.BLOCK_CHECK_FINISHED),
+            entry(LogUtils.Events.DIRECT_TO_VM_FINISHED, AnalyticsEvent.DIRECT_TO_VM_FINISHED),
+            entry(LogUtils.Events.REMOTELY_HELD, AnalyticsEvent.REMOTELY_HELD),
+            entry(LogUtils.Events.REMOTELY_UNHELD, AnalyticsEvent.REMOTELY_UNHELD),
+            entry(LogUtils.Events.REQUEST_PULL, AnalyticsEvent.REQUEST_PULL),
+            entry(LogUtils.Events.REQUEST_ACCEPT, AnalyticsEvent.REQUEST_ACCEPT),
+            entry(LogUtils.Events.REQUEST_REJECT, AnalyticsEvent.REQUEST_REJECT),
+            entry(LogUtils.Events.SET_ACTIVE, AnalyticsEvent.SET_ACTIVE),
+            entry(LogUtils.Events.SET_DISCONNECTED, AnalyticsEvent.SET_DISCONNECTED),
+            entry(LogUtils.Events.SET_HOLD, AnalyticsEvent.SET_HOLD),
+            entry(LogUtils.Events.SET_DIALING, AnalyticsEvent.SET_DIALING),
+            entry(LogUtils.Events.START_CONNECTION, AnalyticsEvent.START_CONNECTION),
+            entry(LogUtils.Events.BIND_CS, AnalyticsEvent.BIND_CS),
+            entry(LogUtils.Events.CS_BOUND, AnalyticsEvent.CS_BOUND),
+            entry(LogUtils.Events.SCREENING_SENT, AnalyticsEvent.SCREENING_SENT),
+            entry(LogUtils.Events.DIRECT_TO_VM_INITIATED,
+                    AnalyticsEvent.DIRECT_TO_VM_INITIATED),
+            entry(LogUtils.Events.BLOCK_CHECK_INITIATED, AnalyticsEvent.BLOCK_CHECK_INITIATED),
+            entry(LogUtils.Events.FILTERING_INITIATED, AnalyticsEvent.FILTERING_INITIATED),
+            entry(LogUtils.Events.FILTERING_COMPLETED, AnalyticsEvent.FILTERING_COMPLETED),
+            entry(LogUtils.Events.FILTERING_TIMED_OUT, AnalyticsEvent.FILTERING_TIMED_OUT),
+            entry(LogUtils.Events.DND_PRE_CHECK_INITIATED, AnalyticsEvent.DND_CHECK_INITIATED),
+            entry(LogUtils.Events.DND_PRE_CHECK_COMPLETED, AnalyticsEvent.DND_CHECK_COMPLETED));
 
     public static final Map<String, Integer> sLogSessionToSessionId = Map.ofEntries(
-                entry(LogUtils.Sessions.ICA_ANSWER_CALL, SessionTiming.ICA_ANSWER_CALL),
-                entry(LogUtils.Sessions.ICA_REJECT_CALL, SessionTiming.ICA_REJECT_CALL),
-                entry(LogUtils.Sessions.ICA_DISCONNECT_CALL, SessionTiming.ICA_DISCONNECT_CALL),
-                entry(LogUtils.Sessions.ICA_HOLD_CALL, SessionTiming.ICA_HOLD_CALL),
-                entry(LogUtils.Sessions.ICA_UNHOLD_CALL, SessionTiming.ICA_UNHOLD_CALL),
-                entry(LogUtils.Sessions.ICA_MUTE, SessionTiming.ICA_MUTE),
-                entry(LogUtils.Sessions.ICA_SET_AUDIO_ROUTE, SessionTiming.ICA_SET_AUDIO_ROUTE),
-                entry(LogUtils.Sessions.ICA_CONFERENCE, SessionTiming.ICA_CONFERENCE),
-                entry(LogUtils.Sessions.CSW_HANDLE_CREATE_CONNECTION_COMPLETE,
-                        SessionTiming.CSW_HANDLE_CREATE_CONNECTION_COMPLETE),
-                entry(LogUtils.Sessions.CSW_SET_ACTIVE, SessionTiming.CSW_SET_ACTIVE),
-                entry(LogUtils.Sessions.CSW_SET_RINGING, SessionTiming.CSW_SET_RINGING),
-                entry(LogUtils.Sessions.CSW_SET_DIALING, SessionTiming.CSW_SET_DIALING),
-                entry(LogUtils.Sessions.CSW_SET_DISCONNECTED, SessionTiming.CSW_SET_DISCONNECTED),
-                entry(LogUtils.Sessions.CSW_SET_ON_HOLD, SessionTiming.CSW_SET_ON_HOLD),
-                entry(LogUtils.Sessions.CSW_REMOVE_CALL, SessionTiming.CSW_REMOVE_CALL),
-                entry(LogUtils.Sessions.CSW_SET_IS_CONFERENCED, SessionTiming.CSW_SET_IS_CONFERENCED),
-                entry(LogUtils.Sessions.CSW_ADD_CONFERENCE_CALL,
-                        SessionTiming.CSW_ADD_CONFERENCE_CALL));
+            entry(LogUtils.Sessions.ICA_ANSWER_CALL, SessionTiming.ICA_ANSWER_CALL),
+            entry(LogUtils.Sessions.ICA_REJECT_CALL, SessionTiming.ICA_REJECT_CALL),
+            entry(LogUtils.Sessions.ICA_DISCONNECT_CALL, SessionTiming.ICA_DISCONNECT_CALL),
+            entry(LogUtils.Sessions.ICA_HOLD_CALL, SessionTiming.ICA_HOLD_CALL),
+            entry(LogUtils.Sessions.ICA_UNHOLD_CALL, SessionTiming.ICA_UNHOLD_CALL),
+            entry(LogUtils.Sessions.ICA_MUTE, SessionTiming.ICA_MUTE),
+            entry(LogUtils.Sessions.ICA_SET_AUDIO_ROUTE, SessionTiming.ICA_SET_AUDIO_ROUTE),
+            entry(LogUtils.Sessions.ICA_CONFERENCE, SessionTiming.ICA_CONFERENCE),
+            entry(LogUtils.Sessions.CSW_HANDLE_CREATE_CONNECTION_COMPLETE,
+                    SessionTiming.CSW_HANDLE_CREATE_CONNECTION_COMPLETE),
+            entry(LogUtils.Sessions.CSW_SET_ACTIVE, SessionTiming.CSW_SET_ACTIVE),
+            entry(LogUtils.Sessions.CSW_SET_RINGING, SessionTiming.CSW_SET_RINGING),
+            entry(LogUtils.Sessions.CSW_SET_DIALING, SessionTiming.CSW_SET_DIALING),
+            entry(LogUtils.Sessions.CSW_SET_DISCONNECTED, SessionTiming.CSW_SET_DISCONNECTED),
+            entry(LogUtils.Sessions.CSW_SET_ON_HOLD, SessionTiming.CSW_SET_ON_HOLD),
+            entry(LogUtils.Sessions.CSW_REMOVE_CALL, SessionTiming.CSW_REMOVE_CALL),
+            entry(LogUtils.Sessions.CSW_SET_IS_CONFERENCED, SessionTiming.CSW_SET_IS_CONFERENCED),
+            entry(LogUtils.Sessions.CSW_ADD_CONFERENCE_CALL,
+                    SessionTiming.CSW_ADD_CONFERENCE_CALL));
 
     public static final Map<String, Integer> sLogEventTimingToAnalyticsEventTiming = Map.ofEntries(
-                entry(LogUtils.Events.Timings.ACCEPT_TIMING,
-                        ParcelableCallAnalytics.EventTiming.ACCEPT_TIMING),
-                entry(LogUtils.Events.Timings.REJECT_TIMING,
-                        ParcelableCallAnalytics.EventTiming.REJECT_TIMING),
-                entry(LogUtils.Events.Timings.DISCONNECT_TIMING,
-                        ParcelableCallAnalytics.EventTiming.DISCONNECT_TIMING),
-                entry(LogUtils.Events.Timings.HOLD_TIMING,
-                        ParcelableCallAnalytics.EventTiming.HOLD_TIMING),
-                entry(LogUtils.Events.Timings.UNHOLD_TIMING,
-                        ParcelableCallAnalytics.EventTiming.UNHOLD_TIMING),
-                entry(LogUtils.Events.Timings.OUTGOING_TIME_TO_DIALING_TIMING,
-                        ParcelableCallAnalytics.EventTiming.OUTGOING_TIME_TO_DIALING_TIMING),
-                entry(LogUtils.Events.Timings.BIND_CS_TIMING,
-                        ParcelableCallAnalytics.EventTiming.BIND_CS_TIMING),
-                entry(LogUtils.Events.Timings.SCREENING_COMPLETED_TIMING,
-                        ParcelableCallAnalytics.EventTiming.SCREENING_COMPLETED_TIMING),
-                entry(LogUtils.Events.Timings.DIRECT_TO_VM_FINISHED_TIMING,
-                        ParcelableCallAnalytics.EventTiming.DIRECT_TO_VM_FINISHED_TIMING),
-                entry(LogUtils.Events.Timings.BLOCK_CHECK_FINISHED_TIMING,
-                        ParcelableCallAnalytics.EventTiming.BLOCK_CHECK_FINISHED_TIMING),
-                entry(LogUtils.Events.Timings.FILTERING_COMPLETED_TIMING,
-                        ParcelableCallAnalytics.EventTiming.FILTERING_COMPLETED_TIMING),
-                entry(LogUtils.Events.Timings.FILTERING_TIMED_OUT_TIMING,
-                        ParcelableCallAnalytics.EventTiming.FILTERING_TIMED_OUT_TIMING),
-                entry(LogUtils.Events.Timings.START_CONNECTION_TO_REQUEST_DISCONNECT_TIMING,
-                        ParcelableCallAnalytics.EventTiming.
-                                START_CONNECTION_TO_REQUEST_DISCONNECT_TIMING));
+            entry(LogUtils.Events.Timings.ACCEPT_TIMING,
+                    ParcelableCallAnalytics.EventTiming.ACCEPT_TIMING),
+            entry(LogUtils.Events.Timings.REJECT_TIMING,
+                    ParcelableCallAnalytics.EventTiming.REJECT_TIMING),
+            entry(LogUtils.Events.Timings.DISCONNECT_TIMING,
+                    ParcelableCallAnalytics.EventTiming.DISCONNECT_TIMING),
+            entry(LogUtils.Events.Timings.HOLD_TIMING,
+                    ParcelableCallAnalytics.EventTiming.HOLD_TIMING),
+            entry(LogUtils.Events.Timings.UNHOLD_TIMING,
+                    ParcelableCallAnalytics.EventTiming.UNHOLD_TIMING),
+            entry(LogUtils.Events.Timings.OUTGOING_TIME_TO_DIALING_TIMING,
+                    ParcelableCallAnalytics.EventTiming.OUTGOING_TIME_TO_DIALING_TIMING),
+            entry(LogUtils.Events.Timings.BIND_CS_TIMING,
+                    ParcelableCallAnalytics.EventTiming.BIND_CS_TIMING),
+            entry(LogUtils.Events.Timings.SCREENING_COMPLETED_TIMING,
+                    ParcelableCallAnalytics.EventTiming.SCREENING_COMPLETED_TIMING),
+            entry(LogUtils.Events.Timings.DIRECT_TO_VM_FINISHED_TIMING,
+                    ParcelableCallAnalytics.EventTiming.DIRECT_TO_VM_FINISHED_TIMING),
+            entry(LogUtils.Events.Timings.BLOCK_CHECK_FINISHED_TIMING,
+                    ParcelableCallAnalytics.EventTiming.BLOCK_CHECK_FINISHED_TIMING),
+            entry(LogUtils.Events.Timings.FILTERING_COMPLETED_TIMING,
+                    ParcelableCallAnalytics.EventTiming.FILTERING_COMPLETED_TIMING),
+            entry(LogUtils.Events.Timings.FILTERING_TIMED_OUT_TIMING,
+                    ParcelableCallAnalytics.EventTiming.FILTERING_TIMED_OUT_TIMING),
+            entry(LogUtils.Events.Timings.START_CONNECTION_TO_REQUEST_DISCONNECT_TIMING,
+                    ParcelableCallAnalytics.EventTiming.
+                            START_CONNECTION_TO_REQUEST_DISCONNECT_TIMING),
+            entry(LogUtils.Events.Timings.DND_PRE_CHECK_COMPLETED_TIMING,
+                    ParcelableCallAnalytics.EventTiming.DND_PRE_CALL_PRE_CHECK_TIMING));
 
     public static final Map<Integer, String> sSessionIdToLogSession = new HashMap<>();
+
     static {
         for (Map.Entry<String, Integer> e : sLogSessionToSessionId.entrySet()) {
             sSessionIdToLogSession.put(e.getValue(), e.getKey());
@@ -228,12 +233,12 @@
         public long startTime;  // start time in milliseconds since the epoch. 0 if not yet set.
         public long endTime;  // end time in milliseconds since the epoch. 0 if not yet set.
         public int callDirection;  // one of UNKNOWN_DIRECTION, INCOMING_DIRECTION,
-                                   // or OUTGOING_DIRECTION.
+        // or OUTGOING_DIRECTION.
         public boolean isAdditionalCall = false;  // true if the call came in while another call was
-                                                  // in progress or if the user dialed this call
-                                                  // while in the middle of another call.
+        // in progress or if the user dialed this call
+        // while in the middle of another call.
         public boolean isInterrupted = false;  // true if the call was interrupted by an incoming
-                                               // or outgoing call.
+        // or outgoing call.
         public int callTechnologies;  // bitmask denoting which technologies a call used.
 
         // true if the Telecom Call object was created from an existing connection via
@@ -432,17 +437,17 @@
             TelecomLogClass.CallLog analyticsProto = toProto();
             List<ParcelableCallAnalytics.AnalyticsEvent> events =
                     Arrays.stream(analyticsProto.callEvents)
-                    .map(callEventProto -> new ParcelableCallAnalytics.AnalyticsEvent(
-                                callEventProto.getEventName(),
-                                callEventProto.getTimeSinceLastEventMillis())
-                    ).collect(Collectors.toList());
+                            .map(callEventProto -> new ParcelableCallAnalytics.AnalyticsEvent(
+                                    callEventProto.getEventName(),
+                                    callEventProto.getTimeSinceLastEventMillis())
+                            ).collect(Collectors.toList());
 
             List<ParcelableCallAnalytics.EventTiming> timings =
                     Arrays.stream(analyticsProto.callTimings)
-                    .map(callTimingProto -> new ParcelableCallAnalytics.EventTiming(
-                            callTimingProto.getTimingName(),
-                            callTimingProto.getTimeMillis())
-                    ).collect(Collectors.toList());
+                            .map(callTimingProto -> new ParcelableCallAnalytics.EventTiming(
+                                    callTimingProto.getTimingName(),
+                                    callTimingProto.getTimeMillis())
+                            ).collect(Collectors.toList());
 
             ParcelableCallAnalytics result = new ParcelableCallAnalytics(
                     // rounds down to nearest 5 minute mark
@@ -498,7 +503,7 @@
                     .setConnectionProperties(callProperties)
                     .setCallSource(callSource);
 
-            result.connectionService = new String[] {connectionService};
+            result.connectionService = new String[]{connectionService};
             if (callEvents != null) {
                 result.callEvents = convertLogEventsToProtoEvents(callEvents.getEvents());
                 result.callTimings = callEvents.extractEventTimings().stream()
@@ -547,7 +552,6 @@
         }
 
         private String getMissedReasonString() {
-            //TODO: Implement this
             StringBuilder s =  new StringBuilder();
             s.append('[');
             if ((missedReason & AUTO_MISSED_EMERGENCY_CALL) != 0) {
@@ -608,6 +612,7 @@
             }
         }
     }
+
     public static final String TAG = "TelecomAnalytics";
 
     // Constants for call direction
@@ -842,13 +847,13 @@
     }
 
     @VisibleForTesting
-    public static long roundToOneSigFig(long val)  {
+    public static long roundToOneSigFig(long val) {
         if (val == 0) {
             return val;
         }
         int logVal = (int) Math.floor(Math.log10(val < 0 ? -val : val));
         double s = Math.pow(10, logVal);
-        double dec =  val / s;
+        double dec = val / s;
         return (long) (Math.round(dec) * s);
     }
 }
diff --git a/src/com/android/server/telecom/AnomalyReporterAdapter.java b/src/com/android/server/telecom/AnomalyReporterAdapter.java
new file mode 100644
index 0000000..7c21419
--- /dev/null
+++ b/src/com/android/server/telecom/AnomalyReporterAdapter.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 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.server.telecom;
+
+import java.util.UUID;
+
+/**
+ * Interface to avoid static calls to AnomalyReporter. Add methods to this interface as needed for
+ * refactoring.
+ */
+public interface AnomalyReporterAdapter {
+    void reportAnomaly(UUID eventId, String description);
+}
diff --git a/src/com/android/server/telecom/AnomalyReporterAdapterImpl.java b/src/com/android/server/telecom/AnomalyReporterAdapterImpl.java
new file mode 100644
index 0000000..c34d211
--- /dev/null
+++ b/src/com/android/server/telecom/AnomalyReporterAdapterImpl.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 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.server.telecom;
+
+import android.telephony.AnomalyReporter;
+import java.util.UUID;
+
+public class AnomalyReporterAdapterImpl implements AnomalyReporterAdapter {
+    @Override
+    public void reportAnomaly(UUID eventId, String description) {
+        AnomalyReporter.reportAnomaly(eventId, description);
+    }
+}
diff --git a/src/com/android/server/telecom/AsyncRingtonePlayer.java b/src/com/android/server/telecom/AsyncRingtonePlayer.java
index 7f51f1b..2712b23 100644
--- a/src/com/android/server/telecom/AsyncRingtonePlayer.java
+++ b/src/com/android/server/telecom/AsyncRingtonePlayer.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.media.AudioAttributes;
 import android.media.Ringtone;
 import android.media.VolumeShaper;
 import android.net.Uri;
@@ -27,12 +26,12 @@
 import android.os.Message;
 import android.telecom.Log;
 import android.telecom.Logging.Session;
-
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.Preconditions;
 
-import java.util.concurrent.CompletableFuture;
+import java.util.function.BiConsumer;
+import java.util.function.Supplier;
 
 /**
  * Plays the default ringtone. Uses {@link Ringtone} in a separate thread so that this class can be
@@ -50,12 +49,6 @@
     /** The current ringtone. Only used by the ringtone thread. */
     private Ringtone mRingtone;
 
-    /**
-     * CompletableFuture which signals a caller when we know whether a ringtone will play haptics
-     * or not.
-     */
-    private CompletableFuture<Boolean> mHapticsFuture = null;
-
     public AsyncRingtonePlayer() {
         // Empty
     }
@@ -65,35 +58,17 @@
      * If {@link VolumeShaper.Configuration} is specified, it is applied to the ringtone to change
      * the volume of the ringtone as it plays.
      *
-     * @param factory The {@link RingtoneFactory}.
-     * @param incomingCall The ringing {@link Call}.
-     * @param volumeShaperConfig An optional {@link VolumeShaper.Configuration} which is applied to
-     *                           the ringtone to change its volume while it rings.
-     * @param isVibrationEnabled {@code true} if the settings and DND configuration of the device
-     *                           is such that the vibrator should be used, {@code false} otherwise.
-     * @return A {@link CompletableFuture} which on completion indicates whether or not the ringtone
-     *         has a haptic track.  {@code True} indicates that a haptic track is present on the
-     *         ringtone; in this case the default vibration in {@link Ringer} should not be played.
-     *         {@code False} indicates that a haptic track is NOT present on the ringtone;
-     *         in this case the default vibration in {@link Ringer} should be trigger if needed.
+     * @param ringtoneSupplier The {@link Ringtone} factory.
+     * @param ringtoneConsumer The {@link Ringtone} post-creation callback (to start the vibration).
      */
-    public @NonNull
-    CompletableFuture<Boolean> play(RingtoneFactory factory, Call incomingCall,
-            @Nullable VolumeShaper.Configuration volumeShaperConfig, boolean isRingerAudible,
-            boolean isVibrationEnabled) {
+    public void play(@NonNull Supplier<Ringtone> ringtoneSupplier,
+            BiConsumer<Ringtone, Boolean> ringtoneConsumer) {
         Log.d(this, "Posting play.");
-        if (mHapticsFuture == null) {
-            mHapticsFuture = new CompletableFuture<>();
-        }
         SomeArgs args = SomeArgs.obtain();
-        args.arg1 = factory;
-        args.arg2 = incomingCall;
-        args.arg3 = volumeShaperConfig;
-        args.arg4 = isVibrationEnabled;
-        args.arg5 = isRingerAudible;
-        args.arg6 = Log.createSubsession();
+        args.arg1 = ringtoneSupplier;
+        args.arg2 = ringtoneConsumer;
+        args.arg3 = Log.createSubsession();
         postMessage(EVENT_PLAY, true /* shouldCreateHandler */, args);
-        return mHapticsFuture;
     }
 
     /** Stops playing the ringtone. */
@@ -151,83 +126,44 @@
      * Starts the actual playback of the ringtone. Executes on ringtone-thread.
      */
     private void handlePlay(SomeArgs args) {
-        RingtoneFactory factory = (RingtoneFactory) args.arg1;
-        Call incomingCall = (Call) args.arg2;
-        VolumeShaper.Configuration volumeShaperConfig = (VolumeShaper.Configuration) args.arg3;
-        boolean isVibrationEnabled = (boolean) args.arg4;
-        boolean isRingerAudible = (boolean) args.arg5;
-        Session session = (Session) args.arg6;
+        Supplier<Ringtone> ringtoneSupplier = (Supplier<Ringtone>) args.arg1;
+        BiConsumer<Ringtone, Boolean> ringtoneConsumer = (BiConsumer<Ringtone, Boolean>) args.arg2;
+        Session session = (Session) args.arg3;
         args.recycle();
 
         Log.continueSession(session, "ARP.hP");
         try {
-            // don't bother with any of this if there is an EVENT_STOP waiting.
+            // Don't bother with any of this if there is an EVENT_STOP waiting, but give the
+            // consumer a chance to do anything no matter what.
             if (mHandler.hasMessages(EVENT_STOP)) {
-                completeHapticFuture(false /* ringtoneHasHaptics */);
+                ringtoneConsumer.accept(null, /* stopped= */ true);
                 return;
             }
+            Ringtone ringtone = null;
+            try {
+                ringtone = ringtoneSupplier.get();
 
-            // If the Ringtone Uri is EMPTY, then the "None" Ringtone has been selected.
-            // If ringer is not audible for this call, then the phone is in "Vibrate" mode.
-            // Use haptic-only ringtone or do not play anything.
-            if (!isRingerAudible || Uri.EMPTY.equals(incomingCall.getRingtone())) {
-                if (isVibrationEnabled) {
-                    setRingtone(factory.getHapticOnlyRingtone());
-                    if (mRingtone == null) {
-                        completeHapticFuture(false /* ringtoneHasHaptics */);
-                        return;
-                    }
-                } else {
-                    setRingtone(null);
-                    completeHapticFuture(false /* ringtoneHasHaptics */);
-                    return;
-                }
-            }
-
-            ThreadUtil.checkNotOnMainThread();
-            Log.i(this, "handlePlay: Play ringtone.");
-
-            if (mRingtone == null) {
-                setRingtone(factory.getRingtone(incomingCall, volumeShaperConfig));
+                // setRingtone even if null - it also stops any current ringtone to be consistent
+                // with the overall state.
+                setRingtone(ringtone);
                 if (mRingtone == null) {
-                    Uri ringtoneUri = incomingCall.getRingtone();
-                    String ringtoneUriString = (ringtoneUri == null) ? "null" :
-                            ringtoneUri.toSafeString();
-                    Log.addEvent(null, LogUtils.Events.ERROR_LOG, "Failed to get ringtone from " +
-                            "factory. Skipping ringing. Uri was: " + ringtoneUriString);
-                    completeHapticFuture(false /* ringtoneHasHaptics */);
+                    // The ringtoneConsumer can still vibrate at this stage.
+                    Log.w(this, "No ringtone was found bail out from playing.");
                     return;
                 }
-            }
-
-            // With the ringtone to play now known, we can determine if it has haptic channels or
-            // not; we will complete the haptics future so the default vibration code in Ringer can
-            // know whether to trigger the vibrator.
-            if (mHapticsFuture != null && !mHapticsFuture.isDone()) {
-                boolean hasHaptics = factory.hasHapticChannels(mRingtone);
-                Log.i(this, "handlePlay: hasHaptics=%b, isVibrationEnabled=%b", hasHaptics,
-                        isVibrationEnabled);
-                SystemSettingsUtil systemSettingsUtil = new SystemSettingsUtil();
-                if (hasHaptics && (volumeShaperConfig == null
-                        || systemSettingsUtil.isAudioCoupledVibrationForRampingRingerEnabled())) {
-                    AudioAttributes attributes = mRingtone.getAudioAttributes();
-                    Log.d(this, "handlePlay: %s haptic channel",
-                            (isVibrationEnabled ? "unmuting" : "muting"));
-                    mRingtone.setAudioAttributes(
-                            new AudioAttributes.Builder(attributes)
-                                    .setHapticChannelsMuted(!isVibrationEnabled)
-                                    .build());
+                Uri uri = mRingtone.getUri();
+                String uriString = (uri != null ? uri.toSafeString() : "");
+                Log.i(this, "handlePlay: Play ringtone. Uri: " + uriString);
+                mRingtone.setLooping(true);
+                if (mRingtone.isPlaying()) {
+                    Log.d(this, "Ringtone already playing.");
+                    return;
                 }
-                completeHapticFuture(hasHaptics);
+                mRingtone.play();
+                Log.i(this, "Play ringtone, looping.");
+            } finally {
+                ringtoneConsumer.accept(ringtone, /* stopped= */ false);
             }
-
-            mRingtone.setLooping(true);
-            if (mRingtone.isPlaying()) {
-                Log.d(this, "Ringtone already playing.");
-                return;
-            }
-            mRingtone.play();
-            Log.i(this, "Play ringtone, looping.");
         } finally {
             Log.cancelSubsession(session);
         }
@@ -268,11 +204,4 @@
         }
         mRingtone = ringtone;
     }
-
-    private void completeHapticFuture(boolean ringtoneHasHaptics) {
-        if (mHapticsFuture != null) {
-            mHapticsFuture.complete(ringtoneHasHaptics);
-            mHapticsFuture = null;
-        }
-    }
 }
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 60016fd..6e5826b 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -17,6 +17,7 @@
 package com.android.server.telecom;
 
 import static android.provider.CallLog.Calls.MISSED_REASON_NOT_MISSED;
+import static android.telecom.Call.EVENT_DISPLAY_SOS_MESSAGE;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -38,6 +39,7 @@
 import android.provider.CallLog;
 import android.provider.ContactsContract.Contacts;
 import android.telecom.BluetoothCallQualityReport;
+import android.telecom.CallAttributes;
 import android.telecom.CallAudioState;
 import android.telecom.CallDiagnosticService;
 import android.telecom.CallDiagnostics;
@@ -68,6 +70,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telecom.IVideoProvider;
 import com.android.internal.util.Preconditions;
+import com.android.server.telecom.stats.CallFailureCause;
+import com.android.server.telecom.stats.CallStateChangedAtomWriter;
 import com.android.server.telecom.ui.ToastFactory;
 
 import java.io.IOException;
@@ -92,7 +96,6 @@
  *  from the time the call intent was received by Telecom (vs. the time the call was
  *  connected etc).
  */
-@VisibleForTesting
 public class Call implements CreateConnectionResponse, EventManager.Loggable,
         ConnectionServiceFocusManager.CallFocus {
     public final static String CALL_ID_UNKNOWN = "-1";
@@ -118,54 +121,60 @@
     /**
      * Listener for events on the call.
      */
-    @VisibleForTesting
     public interface Listener {
-        void onSuccessfulOutgoingCall(Call call, int callState);
-        void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause);
-        void onSuccessfulIncomingCall(Call call);
-        void onFailedIncomingCall(Call call);
-        void onSuccessfulUnknownCall(Call call, int callState);
-        void onFailedUnknownCall(Call call);
-        void onRingbackRequested(Call call, boolean ringbackRequested);
-        void onPostDialWait(Call call, String remaining);
-        void onPostDialChar(Call call, char nextChar);
-        void onConnectionCapabilitiesChanged(Call call);
-        void onConnectionPropertiesChanged(Call call, boolean didRttChange);
-        void onParentChanged(Call call);
-        void onChildrenChanged(Call call);
-        void onCannedSmsResponsesLoaded(Call call);
-        void onVideoCallProviderChanged(Call call);
-        void onCallerInfoChanged(Call call);
-        void onIsVoipAudioModeChanged(Call call);
-        void onStatusHintsChanged(Call call);
-        void onExtrasChanged(Call c, int source, Bundle extras);
-        void onExtrasRemoved(Call c, int source, List<String> keys);
-        void onHandleChanged(Call call);
-        void onCallerDisplayNameChanged(Call call);
-        void onCallDirectionChanged(Call call);
-        void onVideoStateChanged(Call call, int previousVideoState, int newVideoState);
-        void onTargetPhoneAccountChanged(Call call);
-        void onConnectionManagerPhoneAccountChanged(Call call);
-        void onPhoneAccountChanged(Call call);
-        void onConferenceableCallsChanged(Call call);
-        void onConferenceStateChanged(Call call, boolean isConference);
-        void onCdmaConferenceSwap(Call call);
-        boolean onCanceledViaNewOutgoingCallBroadcast(Call call, long disconnectionTimeout);
-        void onHoldToneRequested(Call call);
-        void onCallHoldFailed(Call call);
-        void onCallSwitchFailed(Call call);
-        void onConnectionEvent(Call call, String event, Bundle extras);
-        void onExternalCallChanged(Call call, boolean isExternalCall);
-        void onRttInitiationFailure(Call call, int reason);
-        void onRemoteRttRequest(Call call, int requestId);
-        void onHandoverRequested(Call call, PhoneAccountHandle handoverTo, int videoState,
-                                 Bundle extras, boolean isLegacy);
-        void onHandoverFailed(Call call, int error);
-        void onHandoverComplete(Call call);
-        void onBluetoothCallQualityReport(Call call, BluetoothCallQualityReport report);
-        void onReceivedDeviceToDeviceMessage(Call call, int messageType, int messageValue);
-        void onReceivedCallQualityReport(Call call, CallQuality callQuality);
-        void onCallerNumberVerificationStatusChanged(Call call, int callerNumberVerificationStatus);
+        default void onSuccessfulOutgoingCall(Call call, int callState) {};
+        default void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause) {};
+        default void onSuccessfulIncomingCall(Call call) {};
+        default void onFailedIncomingCall(Call call) {};
+        default void onSuccessfulUnknownCall(Call call, int callState) {};
+        default void onFailedUnknownCall(Call call) {};
+        default void onRingbackRequested(Call call, boolean ringbackRequested) {};
+        default void onPostDialWait(Call call, String remaining) {};
+        default void onPostDialChar(Call call, char nextChar) {};
+        default void onConnectionCapabilitiesChanged(Call call) {};
+        default void onConnectionPropertiesChanged(Call call, boolean didRttChange) {};
+        default void onParentChanged(Call call) {};
+        default void onChildrenChanged(Call call) {};
+        default void onCannedSmsResponsesLoaded(Call call) {};
+        default void onVideoCallProviderChanged(Call call) {};
+        default void onCallerInfoChanged(Call call) {};
+        default void onIsVoipAudioModeChanged(Call call) {};
+        default void onStatusHintsChanged(Call call) {};
+        default void onExtrasChanged(Call c, int source, Bundle extras,
+                String requestingPackageName) {};
+        default void onExtrasRemoved(Call c, int source, List<String> keys) {};
+        default void onHandleChanged(Call call) {};
+        default void onCallerDisplayNameChanged(Call call) {};
+        default void onCallDirectionChanged(Call call) {};
+        default void onVideoStateChanged(Call call, int previousVideoState, int newVideoState) {};
+        default void onTargetPhoneAccountChanged(Call call) {};
+        default void onConnectionManagerPhoneAccountChanged(Call call) {};
+        default void onPhoneAccountChanged(Call call) {};
+        default void onConferenceableCallsChanged(Call call) {};
+        default void onConferenceStateChanged(Call call, boolean isConference) {};
+        default void onCdmaConferenceSwap(Call call) {};
+        default boolean onCanceledViaNewOutgoingCallBroadcast(Call call,
+                long disconnectionTimeout) {
+            return false;
+        };
+        default void onHoldToneRequested(Call call) {};
+        default void onCallHoldFailed(Call call) {};
+        default void onCallSwitchFailed(Call call) {};
+        default void onConnectionEvent(Call call, String event, Bundle extras) {};
+        default void onCallStreamingStateChanged(Call call, boolean isStreaming) {}
+        default void onExternalCallChanged(Call call, boolean isExternalCall) {};
+        default void onRttInitiationFailure(Call call, int reason) {};
+        default void onRemoteRttRequest(Call call, int requestId) {};
+        default void onHandoverRequested(Call call, PhoneAccountHandle handoverTo, int videoState,
+                Bundle extras, boolean isLegacy)  {};
+        default void onHandoverFailed(Call call, int error) {};
+        default void onHandoverComplete(Call call)  {};
+        default void onBluetoothCallQualityReport(Call call, BluetoothCallQualityReport report) {};
+        default void onReceivedDeviceToDeviceMessage(Call call, int messageType,
+                int messageValue) {};
+        default void onReceivedCallQualityReport(Call call, CallQuality callQuality) {};
+        default void onCallerNumberVerificationStatusChanged(Call call,
+                int callerNumberVerificationStatus) {};
     }
 
     public abstract static class ListenerBase implements Listener {
@@ -206,7 +215,8 @@
         @Override
         public void onStatusHintsChanged(Call call) {}
         @Override
-        public void onExtrasChanged(Call c, int source, Bundle extras) {}
+        public void onExtrasChanged(Call c, int source, Bundle extras,
+                String requestingPackageName) {}
         @Override
         public void onExtrasRemoved(Call c, int source, List<String> keys) {}
         @Override
@@ -242,6 +252,8 @@
         @Override
         public void onConnectionEvent(Call call, String event, Bundle extras) {}
         @Override
+        public void onCallStreamingStateChanged(Call call, boolean isStreaming) {}
+        @Override
         public void onExternalCallChanged(Call call, boolean isExternalCall) {}
         @Override
         public void onRttInitiationFailure(Call call, int reason) {}
@@ -353,6 +365,17 @@
     /** The state of the call. */
     private int mState;
 
+    /**
+     * Determines whether the {@link ConnectionService} has responded to the initial request to
+     * create the connection.
+     *
+     * {@code false} indicates the {@link Call} has been added to Telecom, but the
+     * {@link Connection} has not yet been returned by the associated {@link ConnectionService}.
+     * {@code true} indicates the {@link Call} has an associated {@link Connection} reported by the
+     * {@link ConnectionService}.
+     */
+    private boolean mIsCreateConnectionComplete = false;
+
     /** The handle with which to establish this call. */
     private Uri mHandle;
 
@@ -381,6 +404,8 @@
      */
     private ConnectionServiceWrapper mConnectionService;
 
+    private TransactionalServiceWrapper mTransactionalService;
+
     private boolean mIsEmergencyCall;
 
     // The Call is considered an emergency call for testing, but will not actually connect to
@@ -485,6 +510,8 @@
     private final String mId;
     private String mConnectionId;
     private Analytics.CallInfo mAnalytics = new Analytics.CallInfo();
+    private CallStateChangedAtomWriter mCallStateChangedAtomWriter =
+            new CallStateChangedAtomWriter();
     private char mPlayingDtmfTone;
 
     private boolean mWasConferencePreviouslyMerged = false;
@@ -529,6 +556,32 @@
      */
     private boolean mIsSelfManaged = false;
 
+    private boolean mIsTransactionalCall = false;
+    private CallingPackageIdentity mCallingPackageIdentity = new CallingPackageIdentity();
+
+    /**
+     * CallingPackageIdentity is responsible for storing properties about the calling package that
+     * initiated the call. For example, if MyVoipApp requests to add a call with Telecom, we can
+     * store their UID and PID when we are still bound to that package.
+     */
+    public static class CallingPackageIdentity {
+        public int mCallingPackageUid = -1;
+        public int mCallingPackagePid = -1;
+
+        public CallingPackageIdentity() {
+        }
+
+        CallingPackageIdentity(Bundle extras) {
+            mCallingPackageUid = extras.getInt(CallAttributes.CALLER_UID_KEY, -1);
+            mCallingPackagePid = extras.getInt(CallAttributes.CALLER_PID_KEY, -1);
+        }
+    }
+
+    /**
+     * Indicates whether this call is streaming.
+     */
+    private boolean mIsStreaming = false;
+
     /**
      * Indicates whether the {@link PhoneAccount} associated with an self-managed call want to
      * expose the call to an {@link android.telecom.InCallService} which declares the metadata
@@ -771,6 +824,8 @@
         mCreationTimeMillis = mClockProxy.currentTimeMillis();
         mMissedReason = MISSED_REASON_NOT_MISSED;
         mStartRingTime = 0;
+
+        mCallStateChangedAtomWriter.setExistingCallCount(callsManager.getCalls().size());
     }
 
     /**
@@ -1038,10 +1093,9 @@
 
     @Override
     public ConnectionServiceFocusManager.ConnectionServiceFocus getConnectionServiceWrapper() {
-        return mConnectionService;
+        return (!mIsTransactionalCall ? mConnectionService : mTransactionalService);
     }
 
-    @VisibleForTesting
     public int getState() {
         return mState;
     }
@@ -1248,10 +1302,15 @@
                 }
                 Log.addEvent(this, event, stringData);
             }
-            int statsdDisconnectCause = (newState == CallState.DISCONNECTED) ?
-                    getDisconnectCause().getCode() : DisconnectCause.UNKNOWN;
-            TelecomStatsLog.write(TelecomStatsLog.CALL_STATE_CHANGED, newState,
-                    statsdDisconnectCause, isSelfManaged(), isExternalCall());
+
+            mCallStateChangedAtomWriter
+                    .setDisconnectCause(getDisconnectCause())
+                    .setSelfManaged(isSelfManaged())
+                    .setExternalCall(isExternalCall())
+                    .setEmergencyCall(isEmergencyCall())
+                    .setDurationSeconds(Long.valueOf(
+                        (mDisconnectTimeMillis - mConnectTimeMillis) / 1000).intValue())
+                    .write(newState);
         }
         return true;
     }
@@ -1272,13 +1331,34 @@
         Bundle bundle = new Bundle();
         bundle.putBoolean(android.telecom.Call.EXTRA_SILENT_RINGING_REQUESTED,
                 silentRingingRequested);
-        putExtras(SOURCE_CONNECTION_SERVICE, bundle);
+        putConnectionServiceExtras(bundle);
     }
 
     public boolean isSilentRingingRequested() {
         return mSilentRingingRequested;
     }
 
+    public void setCallIsSuppressedByDoNotDisturb(boolean isCallSuppressed) {
+        Bundle bundle = new Bundle();
+        bundle.putBoolean(android.telecom.Call.EXTRA_IS_SUPPRESSED_BY_DO_NOT_DISTURB,
+                isCallSuppressed);
+        putConnectionServiceExtras(bundle);
+    }
+
+    public boolean isCallSuppressedByDoNotDisturb() {
+        if (getExtras() == null) {
+            return false;
+        }
+        return getExtras().getBoolean(android.telecom.Call.EXTRA_IS_SUPPRESSED_BY_DO_NOT_DISTURB);
+    }
+
+    public boolean wasDndCheckComputedForCall() {
+        if (getExtras() == null) {
+            return false;
+        }
+        return getExtras().containsKey(android.telecom.Call.EXTRA_IS_SUPPRESSED_BY_DO_NOT_DISTURB);
+    }
+
     @VisibleForTesting
     public boolean isConference() {
         return mIsConference;
@@ -1401,6 +1481,10 @@
         }
     }
 
+    public Uri getContactPhotoUri() {
+        return mCallerInfo != null ? mCallerInfo.getContactDisplayPhotoUri() : null;
+    }
+
     public String getCallerDisplayName() {
         return mCallerDisplayName;
     }
@@ -1420,6 +1504,12 @@
         }
     }
 
+    void setContactPhotoUri(Uri contactPhotoUri) {
+        if (mCallerInfo != null) {
+            mCallerInfo.SetContactDisplayPhotoUri(contactPhotoUri);
+        }
+    }
+
     public String getName() {
         return mCallerInfo == null ? null : mCallerInfo.getName();
     }
@@ -1472,12 +1562,20 @@
      * @return {@code true} if this is an outgoing call to emergency services. An outgoing call is
      * identified as an emergency call by the dialer phone number.
      */
-    @VisibleForTesting
     public boolean isEmergencyCall() {
         return mIsEmergencyCall;
     }
 
     /**
+     * For testing purposes, set if this call is an emergency call or not.
+     * @param isEmergencyCall {@code true} if emergency, {@code false} otherwise.
+     */
+    @VisibleForTesting
+    public void setIsEmergencyCall(boolean isEmergencyCall) {
+        mIsEmergencyCall = isEmergencyCall;
+    }
+
+    /**
      * @return {@code true} if this an outgoing call to a test emergency number (and NOT to
      * emergency services). Used for testing purposes to differentiate between a real and fake
      * emergency call for safety reasons during testing.
@@ -1584,6 +1682,32 @@
         }
         checkIfVideoCapable();
         checkIfRttCapable();
+
+        if (accountHandle != null) {
+            mCallStateChangedAtomWriter.setUid(
+                    accountHandle.getComponentName().getPackageName(),
+                    mContext.getPackageManager());
+        }
+    }
+
+    public UserHandle getUserHandleFromTargetPhoneAccount() {
+        return mTargetPhoneAccountHandle == null
+                ? mCallsManager.getCurrentUserHandle() :
+                mTargetPhoneAccountHandle.getUserHandle();
+    }
+
+    public PhoneAccount getPhoneAccountFromHandle() {
+        if (getTargetPhoneAccount() == null) {
+            return null;
+        }
+        PhoneAccount phoneAccount = mCallsManager.getPhoneAccountRegistrar()
+                .getPhoneAccountUnchecked(getTargetPhoneAccount());
+
+        if (phoneAccount == null) {
+            return null;
+        }
+
+        return phoneAccount;
     }
 
     public CharSequence getTargetPhoneAccountLabel() {
@@ -1716,6 +1840,36 @@
         setConnectionProperties(getConnectionProperties());
     }
 
+    public boolean isTransactionalCall() {
+        return mIsTransactionalCall;
+    }
+
+    public void setIsTransactionalCall(boolean isTransactionalCall) {
+        mIsTransactionalCall = isTransactionalCall;
+
+        // Connection properties will add/remove the PROPERTY_SELF_MANAGED.
+        setConnectionProperties(getConnectionProperties());
+    }
+
+    public void setCallingPackageIdentity(Bundle extras) {
+        mCallingPackageIdentity = new CallingPackageIdentity(extras);
+        // These extras should NOT be propagated to Dialer and should be removed.
+        extras.remove(CallAttributes.CALLER_PID_KEY);
+        extras.remove(CallAttributes.CALLER_UID_KEY);
+    }
+
+    public CallingPackageIdentity getCallingPackageIdentity() {
+        return mCallingPackageIdentity;
+    }
+
+    public void setTransactionServiceWrapper(TransactionalServiceWrapper service) {
+        mTransactionalService = service;
+    }
+
+    public TransactionalServiceWrapper getTransactionServiceWrapper() {
+        return mTransactionalService;
+    }
+
     public boolean visibleToInCallService() {
         return mVisibleToInCallService;
     }
@@ -1993,8 +2147,10 @@
                     createRttStreams();
                     // Call startRtt to pass the RTT pipes down to the connection service.
                     // They already turned on the RTT property so no request should be sent.
-                    mConnectionService.startRtt(this,
-                            getInCallToCsRttPipeForCs(), getCsToInCallRttPipeForCs());
+                    if (mConnectionService != null) {
+                        mConnectionService.startRtt(this,
+                                getInCallToCsRttPipeForCs(), getCsToInCallRttPipeForCs());
+                    }
                     mWasEverRtt = true;
                     if (isEmergencyCall()) {
                         mCallsManager.mute(false);
@@ -2095,7 +2251,6 @@
         return mConferenceLevelActiveCall;
     }
 
-    @VisibleForTesting
     public ConnectionServiceWrapper getConnectionService() {
         return mConnectionService;
     }
@@ -2192,6 +2347,7 @@
             CallIdMapper idMapper,
             ParcelableConference conference) {
         Log.v(this, "handleCreateConferenceSuccessful %s", conference);
+        mIsCreateConnectionComplete = true;
         setTargetPhoneAccount(conference.getPhoneAccount());
         setHandle(conference.getHandle(), conference.getHandlePresentation());
 
@@ -2201,7 +2357,7 @@
         setVideoState(conference.getVideoState());
         setRingbackRequested(conference.isRingbackRequested());
         setStatusHints(conference.getStatusHints());
-        putExtras(SOURCE_CONNECTION_SERVICE, conference.getExtras());
+        putConnectionServiceExtras(conference.getExtras());
 
         switch (mCallDirection) {
             case CALL_DIRECTION_INCOMING:
@@ -2225,11 +2381,12 @@
             CallIdMapper idMapper,
             ParcelableConnection connection) {
         Log.v(this, "handleCreateConnectionSuccessful %s", connection);
+        mIsCreateConnectionComplete = true;
         setTargetPhoneAccount(connection.getPhoneAccount());
         setHandle(connection.getHandle(), connection.getHandlePresentation());
+
         setCallerDisplayName(
                 connection.getCallerDisplayName(), connection.getCallerDisplayNamePresentation());
-
         setConnectionCapabilities(connection.getConnectionCapabilities());
         setConnectionProperties(connection.getConnectionProperties());
         setIsVoipAudioMode(connection.getIsVoipAudioMode());
@@ -2238,7 +2395,7 @@
         setVideoState(connection.getVideoState());
         setRingbackRequested(connection.isRingbackRequested());
         setStatusHints(connection.getStatusHints());
-        putExtras(SOURCE_CONNECTION_SERVICE, connection.getExtras());
+        putConnectionServiceExtras(connection.getExtras());
 
         mConferenceableCalls.clear();
         for (String id : connection.getConferenceableConnectionIds()) {
@@ -2272,6 +2429,8 @@
 
     @Override
     public void handleCreateConferenceFailure(DisconnectCause disconnectCause) {
+        Log.i(this, "handleCreateConferenceFailure; callid=%s, disconnectCause=%s",
+                getId(), disconnectCause);
         clearConnectionService();
         setDisconnectCause(disconnectCause);
         mCallsManager.markCallAsDisconnected(this, disconnectCause);
@@ -2292,6 +2451,8 @@
 
     @Override
     public void handleCreateConnectionFailure(DisconnectCause disconnectCause) {
+        Log.i(this, "handleCreateConnectionFailure; callid=%s, disconnectCause=%s",
+                getId(), disconnectCause);
         clearConnectionService();
         setDisconnectCause(disconnectCause);
         mCallsManager.markCallAsDisconnected(this, disconnectCause);
@@ -2371,7 +2532,6 @@
         disconnect(0);
     }
 
-    @VisibleForTesting
     public void disconnect(String reason) {
         disconnect(0, reason);
     }
@@ -2400,7 +2560,7 @@
 
         if (mState == CallState.NEW || mState == CallState.SELECT_PHONE_ACCOUNT ||
                 mState == CallState.CONNECTING) {
-            Log.v(this, "Aborting call %s", this);
+            Log.i(this, "disconnect: Aborting call %s", getId());
             abort(disconnectionTimeout);
         } else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) {
             if (mState == CallState.AUDIO_PROCESSING && !hasGoneActiveBefore()) {
@@ -2411,7 +2571,10 @@
                 // Override the disconnect cause to MISSED
                 setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.MISSED));
             }
-            if (mConnectionService == null) {
+            if (mTransactionalService != null) {
+                mTransactionalService.onDisconnect(this, getDisconnectCause());
+                Log.i(this, "Send Disconnect to transactional service for call");
+            } else if (mConnectionService == null) {
                 Log.e(this, new Exception(), "disconnect() request on a call without a"
                         + " connection service.");
             } else {
@@ -2480,6 +2643,8 @@
             // {@link ConnectionServiceAdapter#setActive} and other set* methods.
             if (mConnectionService != null) {
                 mConnectionService.answer(this, videoState);
+            } else if (mTransactionalService != null) {
+                mTransactionalService.onAnswer(this, videoState);
             } else {
                 Log.e(this, new NullPointerException(),
                         "answer call failed due to null CS callId=%s", getId());
@@ -2571,7 +2736,10 @@
             // ringing. Since the call is already active on the connectionservice side, we want to
             // hangup, not reject.
             setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.REJECTED));
-            if (mConnectionService != null) {
+            if (mTransactionalService != null) {
+                mTransactionalService.onDisconnect(this,
+                        new DisconnectCause(DisconnectCause.REJECTED));
+            } else if (mConnectionService != null) {
                 mConnectionService.disconnect(this);
             } else {
                 Log.e(this, new NullPointerException(),
@@ -2582,7 +2750,10 @@
             // Ensure video state history tracks video state at time of rejection.
             mVideoStateHistory |= mVideoState;
 
-            if (mConnectionService != null) {
+            if (mTransactionalService != null) {
+                mTransactionalService.onDisconnect(this,
+                        new DisconnectCause(DisconnectCause.REJECTED));
+            } else if (mConnectionService != null) {
                 mConnectionService.reject(this, rejectWithMessage, textMessage);
             } else {
                 Log.e(this, new NullPointerException(),
@@ -2603,7 +2774,10 @@
             // hangup, not reject.
             // Since its simulated reason we can't pass along the reject reason.
             setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.REJECTED));
-            if (mConnectionService != null) {
+            if (mTransactionalService != null) {
+                mTransactionalService.onDisconnect(this,
+                        new DisconnectCause(DisconnectCause.REJECTED));
+            } else if (mConnectionService != null) {
                 mConnectionService.disconnect(this);
             } else {
                 Log.e(this, new NullPointerException(),
@@ -2613,8 +2787,10 @@
         } else if (isRinging("reject") || isAnswered("reject")) {
             // Ensure video state history tracks video state at time of rejection.
             mVideoStateHistory |= mVideoState;
-
-            if (mConnectionService != null) {
+            if (mTransactionalService != null) {
+                mTransactionalService.onDisconnect(this,
+                        new DisconnectCause(DisconnectCause.REJECTED));
+            } else if (mConnectionService != null) {
                 mConnectionService.rejectWithReason(this, rejectReason);
             } else {
                 Log.e(this, new NullPointerException(),
@@ -2633,7 +2809,9 @@
     @VisibleForTesting
     public void transfer(Uri number, boolean isConfirmationRequired) {
         if (mState == CallState.ACTIVE || mState == CallState.ON_HOLD) {
-            if (mConnectionService != null) {
+            if (mTransactionalService != null) {
+                Log.i(this, "transfer: called on TransactionalService. doing nothing");
+            } else if (mConnectionService != null) {
                 mConnectionService.transfer(this, number, isConfirmationRequired);
             } else {
                 Log.e(this, new NullPointerException(),
@@ -2653,7 +2831,9 @@
     public void transfer(Call otherCall) {
         if (mState == CallState.ACTIVE &&
                 (otherCall != null && otherCall.getState() == CallState.ON_HOLD)) {
-            if (mConnectionService != null) {
+            if (mTransactionalService != null) {
+                Log.i(this, "transfer: called on TransactionalService. doing nothing");
+            } else if (mConnectionService != null) {
                 mConnectionService.transfer(this, otherCall);
             } else {
                 Log.e(this, new NullPointerException(),
@@ -2673,7 +2853,9 @@
 
     public void hold(String reason) {
         if (mState == CallState.ACTIVE) {
-            if (mConnectionService != null) {
+            if (mTransactionalService != null) {
+                mTransactionalService.onSetInactive(this);
+            } else if (mConnectionService != null) {
                 mConnectionService.hold(this);
             } else {
                 Log.e(this, new NullPointerException(),
@@ -2693,7 +2875,9 @@
 
     public void unhold(String reason) {
         if (mState == CallState.ON_HOLD) {
-            if (mConnectionService != null) {
+            if (mTransactionalService != null){
+                mTransactionalService.onSetActive(this);
+            } else if (mConnectionService != null){
                 mConnectionService.unhold(this);
             } else {
                 Log.e(this, new NullPointerException(),
@@ -2718,7 +2902,6 @@
         }
     }
 
-    @VisibleForTesting
     public boolean isActive() {
         return mState == CallState.ACTIVE;
     }
@@ -2729,18 +2912,45 @@
     }
 
     /**
+     * Adds extras to the extras bundle associated with this {@link Call}, as made by a
+     * {@link ConnectionService} or other non {@link android.telecom.InCallService} source.
+     *
+     * @param extras The extras.
+     */
+    public void putConnectionServiceExtras(Bundle extras) {
+        putExtras(SOURCE_CONNECTION_SERVICE, extras, null);
+    }
+
+    /**
+     * Adds extras to the extras bundle associated with this {@link Call}, as made by a
+     * {@link android.telecom.InCallService}.
+     * @param extras the extras.
+     * @param requestingPackageName the package name of the {@link android.telecom.InCallService}
+     *                              which requested the extras changed; required so that when we
+     *                              have {@link InCallController} notify other
+     *                              {@link android.telecom.InCallService}s we don't notify the
+     *                              originator of their own change.
+     */
+    public void putInCallServiceExtras(Bundle extras, String requestingPackageName) {
+        putExtras(SOURCE_INCALL_SERVICE, extras, requestingPackageName);
+    }
+
+    /**
      * Adds extras to the extras bundle associated with this {@link Call}.
      *
      * Note: this method needs to know the source of the extras change (see
      * {@link #SOURCE_CONNECTION_SERVICE}, {@link #SOURCE_INCALL_SERVICE}).  Extras changes which
-     * originate from a connection service will only be notified to incall services.  Likewise,
-     * changes originating from the incall services will only notify the connection service of the
-     * change.
+     * originate from a connection service will only be notified to incall services.  Changes
+     * originating from the InCallServices will notify the connection service of the
+     * change, as well as other InCallServices other than the originator.
      *
      * @param source The source of the extras addition.
      * @param extras The extras.
+     * @param requestingPackageName The package name which requested the extras change.  For
+     *                              {@link #SOURCE_INCALL_SERVICE} will be populated with the
+     *                              package name of the ICS that requested the change.
      */
-    public void putExtras(int source, Bundle extras) {
+    private void putExtras(int source, Bundle extras, String requestingPackageName) {
         if (extras == null) {
             return;
         }
@@ -2750,7 +2960,7 @@
         mExtras.putAll(extras);
 
         for (Listener l : mListeners) {
-            l.onExtrasChanged(this, source, extras);
+            l.onExtrasChanged(this, source, extras, requestingPackageName);
         }
 
         // If mExtra shows that the call using Volte, record it with mWasVolte
@@ -2784,7 +2994,10 @@
 
         // If the change originated from an InCallService, notify the connection service.
         if (source == SOURCE_INCALL_SERVICE) {
-            if (mConnectionService != null) {
+            Log.addEvent(this, LogUtils.Events.ICS_EXTRAS_CHANGED);
+            if (mTransactionalService != null) {
+                Log.i(this, "putExtras: called on TransactionalService. doing nothing");
+            } else if (mConnectionService != null) {
                 mConnectionService.onExtrasChanged(this, mExtras);
             } else {
                 Log.e(this, new NullPointerException(),
@@ -2819,7 +3032,9 @@
 
         // If the change originated from an InCallService, notify the connection service.
         if (source == SOURCE_INCALL_SERVICE) {
-            if (mConnectionService != null) {
+            if (mTransactionalService != null) {
+                Log.i(this, "removeExtras: called on TransactionalService. doing nothing");
+            } else if (mConnectionService != null) {
                 mConnectionService.onExtrasChanged(this, mExtras);
             } else {
                 Log.e(this, new NullPointerException(),
@@ -2873,7 +3088,9 @@
     }
 
     void postDialContinue(boolean proceed) {
-        if (mConnectionService != null) {
+        if (mTransactionalService != null) {
+            Log.i(this, "postDialContinue: called on TransactionalService. doing nothing");
+        } else if (mConnectionService != null) {
             mConnectionService.onPostDialContinue(this, proceed);
         } else {
             Log.e(this, new NullPointerException(),
@@ -2882,7 +3099,9 @@
     }
 
     void conferenceWith(Call otherCall) {
-        if (mConnectionService == null) {
+        if (mTransactionalService != null) {
+            Log.i(this, "conferenceWith: called on TransactionalService. doing nothing");
+        } else if (mConnectionService == null) {
             Log.w(this, "conference requested on a call without a connection service.");
         } else {
             Log.addEvent(this, LogUtils.Events.CONFERENCE_WITH, otherCall);
@@ -2891,7 +3110,9 @@
     }
 
     void splitFromConference() {
-        if (mConnectionService == null) {
+        if (mTransactionalService != null) {
+            Log.i(this, "splitFromConference: called on TransactionalService. doing nothing");
+        } else if (mConnectionService == null) {
             Log.w(this, "splitting from conference call without a connection service");
         } else {
             Log.addEvent(this, LogUtils.Events.SPLIT_FROM_CONFERENCE);
@@ -2901,7 +3122,9 @@
 
     @VisibleForTesting
     public void mergeConference() {
-        if (mConnectionService == null) {
+        if (mTransactionalService != null) {
+            Log.i(this, "mergeConference: called on TransactionalService. doing nothing");
+        } else if (mConnectionService == null) {
             Log.w(this, "merging conference calls without a connection service.");
         } else if (can(Connection.CAPABILITY_MERGE_CONFERENCE)) {
             Log.addEvent(this, LogUtils.Events.CONFERENCE_WITH);
@@ -2912,7 +3135,9 @@
 
     @VisibleForTesting
     public void swapConference() {
-        if (mConnectionService == null) {
+        if (mTransactionalService != null) {
+            Log.i(this, "swapConference: called on TransactionalService. doing nothing");
+        } else if (mConnectionService == null) {
             Log.w(this, "swapping conference calls without a connection service.");
         } else if (can(Connection.CAPABILITY_SWAP_CONFERENCE)) {
             Log.addEvent(this, LogUtils.Events.SWAP);
@@ -2938,7 +3163,9 @@
     }
 
     public void addConferenceParticipants(List<Uri> participants) {
-        if (mConnectionService == null) {
+        if (mTransactionalService != null) {
+            Log.i(this, "addConferenceParticipants: called on TransactionalService. doing nothing");
+        } else if (mConnectionService == null) {
             Log.w(this, "adding conference participants without a connection service.");
         } else if (can(Connection.CAPABILITY_ADD_PARTICIPANT)) {
             Log.addEvent(this, LogUtils.Events.ADD_PARTICIPANT);
@@ -2966,6 +3193,11 @@
      * If there is an ongoing emergency call, pull requests are also ignored.
      */
     public void pullExternalCall() {
+        if (mTransactionalService != null) {
+            Log.i(this, "transfer: called on TransactionalService. doing nothing");
+            return;
+        }
+
         if (mConnectionService == null) {
             Log.w(this, "pulling a call without a connection service.");
         }
@@ -3013,7 +3245,7 @@
      * @param extras Associated extras.
      */
     public void sendCallEvent(String event, int targetSdkVer, Bundle extras) {
-        if (mConnectionService != null) {
+        if (mConnectionService != null || mTransactionalService != null) {
             if (android.telecom.Call.EVENT_REQUEST_HANDOVER.equals(event)) {
                 if (targetSdkVer > Build.VERSION_CODES.P) {
                     Log.e(this, new Exception(), "sendCallEvent failed. Use public api handoverTo" +
@@ -3036,8 +3268,8 @@
                 if (extras == null) {
                     Log.w(this, "sendCallEvent: %s event received with null extras.",
                             android.telecom.Call.EVENT_REQUEST_HANDOVER);
-                    mConnectionService.sendCallEvent(this,
-                            android.telecom.Call.EVENT_HANDOVER_FAILED, null);
+                    sendEventToService(this, android.telecom.Call.EVENT_HANDOVER_FAILED,
+                            null);
                     return;
                 }
                 Parcelable parcelable = extras.getParcelable(
@@ -3045,8 +3277,7 @@
                 if (!(parcelable instanceof PhoneAccountHandle) || parcelable == null) {
                     Log.w(this, "sendCallEvent: %s event received with invalid handover acct.",
                             android.telecom.Call.EVENT_REQUEST_HANDOVER);
-                    mConnectionService.sendCallEvent(this,
-                            android.telecom.Call.EVENT_HANDOVER_FAILED, null);
+                    sendEventToService(this, android.telecom.Call.EVENT_HANDOVER_FAILED, null);
                     return;
                 }
                 PhoneAccountHandle phoneAccountHandle = (PhoneAccountHandle) parcelable;
@@ -3069,7 +3300,7 @@
                     ));
                 }
                 Log.addEvent(this, LogUtils.Events.CALL_EVENT, event);
-                mConnectionService.sendCallEvent(this, event, extras);
+                sendEventToService(this, event, extras);
             }
         } else {
             Log.e(this, new NullPointerException(),
@@ -3078,6 +3309,17 @@
     }
 
     /**
+     *  This method should only be called from sendCallEvent(String, int, Bundle).
+     */
+    private void sendEventToService(Call call, String event, Bundle extras) {
+        if (mConnectionService != null) {
+            mConnectionService.sendCallEvent(call, event, extras);
+        } else if (mTransactionalService != null) {
+            mTransactionalService.onEvent(call, event, extras);
+        }
+    }
+
+    /**
      * Notifies listeners when a bluetooth quality report is received.
      * @param report The bluetooth quality report.
      */
@@ -3373,11 +3615,14 @@
             return;
         }
 
+        String newName = callerInfo.getName();
+        boolean contactNameChanged = mCallerInfo == null || !mCallerInfo.getName().equals(newName);
+
         mCallerInfo = callerInfo;
         Log.i(this, "CallerInfo received for %s: %s", Log.piiHandle(mHandle), callerInfo);
 
-        if (mCallerInfo.getContactDisplayPhotoUri() == null ||
-                mCallerInfo.cachedPhotoIcon != null || mCallerInfo.cachedPhoto != null) {
+        if (mCallerInfo.getContactDisplayPhotoUri() == null || mCallerInfo.cachedPhotoIcon != null
+            || mCallerInfo.cachedPhoto != null || contactNameChanged) {
             for (Listener l : mListeners) {
                 l.onCallerInfoChanged(this);
             }
@@ -3443,7 +3688,10 @@
     }
 
     public void stopRtt() {
-        if (mConnectionService != null) {
+        if (mTransactionalService != null) {
+            Log.i(this, "stopRtt: called on TransactionalService. doing nothing");
+        } else if (mConnectionService != null) {
+            Log.addEvent(this, LogUtils.Events.REQUEST_RTT, "stop");
             mConnectionService.stopRtt(this);
         } else {
             // If this gets called by the in-call app before the connection service is set, we'll
@@ -3453,6 +3701,11 @@
     }
 
     public void sendRttRequest() {
+        if (mTransactionalService != null) {
+            Log.i(this, "sendRttRequest: called on TransactionalService. doing nothing");
+            return;
+        }
+        Log.addEvent(this, LogUtils.Events.REQUEST_RTT, "start");
         createRttStreams();
         mConnectionService.startRtt(this, getInCallToCsRttPipeForCs(), getCsToInCallRttPipeForCs());
     }
@@ -3476,12 +3729,14 @@
 
     public void onRttConnectionFailure(int reason) {
         Log.i(this, "Got RTT initiation failure with reason %d", reason);
+        Log.addEvent(this, LogUtils.Events.ON_RTT_FAILED, "reason="  + reason);
         for (Listener l : mListeners) {
             l.onRttInitiationFailure(this, reason);
         }
     }
 
     public void onRemoteRttRequest() {
+        Log.addEvent(this, LogUtils.Events.ON_RTT_REQUEST);
         if (isRttCall()) {
             Log.w(this, "Remote RTT request on a call that's already RTT");
             return;
@@ -3502,6 +3757,12 @@
             Log.w(this, "Response ID %d does not match expected %d", id, mPendingRttRequestId);
             return;
         }
+        if (mTransactionalService != null) {
+            Log.i(this, "handleRttRequestResponse: called on TransactionalService. doing nothing");
+            return;
+        }
+        Log.addEvent(this, LogUtils.Events.RESPOND_TO_RTT_REQUEST, "id=" + id + ", accept="
+                + accept);
         if (accept) {
             createRttStreams();
             Log.i(this, "RTT request %d accepted.", id);
@@ -3654,6 +3915,12 @@
     }
 
     public void setIsVoipAudioMode(boolean audioModeIsVoip) {
+        if (isSelfManaged() && !audioModeIsVoip) {
+            Log.i(this,
+                    "setIsVoipAudioMode: ignoring request to set self-managed audio to "
+                            + "non-voip mode");
+            return;
+        }
         if (mIsVoipAudioMode != audioModeIsVoip) {
             Log.addEvent(this, LogUtils.Events.SET_VOIP_MODE, audioModeIsVoip ? "Y" : "N");
         }
@@ -3678,6 +3945,10 @@
         return mCallDirection == CALL_DIRECTION_UNKNOWN;
     }
 
+    public boolean isOutgoing() {
+        return mCallDirection == CALL_DIRECTION_OUTGOING;
+    }
+
     /**
      * Determines if this call is in a disconnecting state.
      *
@@ -3772,7 +4043,8 @@
 
     public void setRttMode(int mode) {
         mRttMode = mode;
-        // TODO: hook this up to CallAudioManager
+        Log.addEvent(this, LogUtils.Events.SET_RRT_MODE, "mode=" + mode);
+        // TODO: hook this up to CallAudioManager.
     }
 
     /**
@@ -3847,6 +4119,12 @@
                 l.onReceivedCallQualityReport(this, callQuality);
             }
         } else {
+            if (event.equals(EVENT_DISPLAY_SOS_MESSAGE) && !isEmergencyCall()) {
+                Log.w(this, "onConnectionEvent: EVENT_DISPLAY_SOS_MESSAGE is sent "
+                        + "without an emergency call");
+                return;
+            }
+
             for (Listener l : mListeners) {
                 l.onConnectionEvent(this, event, extras);
             }
@@ -4162,7 +4440,20 @@
         mCallScreeningComponentName = callScreeningComponentName;
     }
 
+    public void setStartFailCause(CallFailureCause cause) {
+        mCallStateChangedAtomWriter.setStartFailCause(cause);
+    }
+
+    public void increaseHeldByThisCallCount() {
+        mCallStateChangedAtomWriter.increaseHeldCallCount();
+    }
+
     public void maybeOnInCallServiceTrackingChanged(boolean isTracking, boolean hasUi) {
+        if (mTransactionalService != null) {
+            Log.i(this,
+                    "maybeOnInCallServiceTrackingChanged: called on TransactionalService");
+            return;
+        }
         if (mConnectionService == null) {
             Log.w(this, "maybeOnInCallServiceTrackingChanged() request on a call"
                     + " without a connection service.");
@@ -4248,4 +4539,56 @@
             mDisconnectFuture = null;
         }
     }
+
+    /**
+     * @return {@code true} if the connection has been created by the underlying
+     * {@link ConnectionService}, {@code false} otherwise.
+     */
+    public boolean isCreateConnectionComplete() {
+        return mIsCreateConnectionComplete;
+    }
+
+    @VisibleForTesting
+    public void setIsCreateConnectionComplete(boolean isCreateConnectionComplete) {
+        mIsCreateConnectionComplete = isCreateConnectionComplete;
+    }
+
+    public boolean isStreaming() {
+        synchronized (mLock) {
+            return mIsStreaming;
+        }
+    }
+
+    public void startStreaming() {
+        if (!mIsTransactionalCall) {
+            throw new UnsupportedOperationException(
+                    "Can't streaming call created by non voip apps");
+        }
+
+        synchronized (mLock) {
+            if (mIsStreaming) {
+                // ignore
+                return;
+            }
+
+            mIsStreaming = true;
+            for (Listener listener : mListeners) {
+                listener.onCallStreamingStateChanged(this, true /** isStreaming */);
+            }
+        }
+    }
+
+    public void stopStreaming() {
+        synchronized (mLock) {
+            if (!mIsStreaming) {
+                // ignore
+                return;
+            }
+
+            mIsStreaming = false;
+            for (Listener listener : mListeners) {
+                listener.onCallStreamingStateChanged(this, false /** isStreaming */);
+            }
+        }
+    }
 }
diff --git a/src/com/android/server/telecom/CallAnomalyWatchdog.java b/src/com/android/server/telecom/CallAnomalyWatchdog.java
new file mode 100644
index 0000000..045671e
--- /dev/null
+++ b/src/com/android/server/telecom/CallAnomalyWatchdog.java
@@ -0,0 +1,440 @@
+/*
+ * Copyright (C) 2022 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.server.telecom;
+
+import static com.android.server.telecom.LogUtils.Events.STATE_TIMEOUT;
+
+import android.provider.DeviceConfig;
+import android.telecom.ConnectionService;
+import android.telecom.DisconnectCause;
+import android.telecom.Log;
+import android.util.LocalLog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.telecom.stats.CallStateChangedAtomWriter;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+/**
+ * Watchdog class responsible for detecting potential anomalous conditions for {@link Call}s.
+ */
+public class CallAnomalyWatchdog extends CallsManagerListenerBase implements Call.Listener {
+    private final EmergencyCallDiagnosticLogger mEmergencyCallDiagnosticLogger;
+
+    /**
+     * Class used to track the call state as it pertains to the watchdog. The watchdog cares about
+     * both the call state and whether a {@link ConnectionService} has finished creating the
+     * connection.
+     */
+    public static class WatchdogCallState {
+        public final int state;
+        public final boolean isCreateConnectionComplete;
+        public final long stateStartTimeMillis;
+
+        public WatchdogCallState(int newState, boolean newIsCreateConnectionComplete,
+                long newStateStartTimeMillis) {
+            state = newState;
+            isCreateConnectionComplete = newIsCreateConnectionComplete;
+            stateStartTimeMillis = newStateStartTimeMillis;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (!(o instanceof WatchdogCallState)) return false;
+            WatchdogCallState that = (WatchdogCallState) o;
+            // don't include the state timestamp in the equality check.
+            return state == that.state
+                    && isCreateConnectionComplete == that.isCreateConnectionComplete;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(state, isCreateConnectionComplete);
+        }
+
+        @Override
+        public String toString() {
+            return "[isCreateConnComplete=" + isCreateConnectionComplete + ", state="
+                    + CallState.toString(state) + "]";
+        }
+
+        /**
+         * Determines if the current call is in a transitory state.  A call is deemed to be in a
+         * transitory state if either {@link CallState#isTransitoryState(int)} returns true, OR
+         * if the call has been created but is not yet added to {@link CallsManager} (i.e. we are
+         * still waiting for the {@link ConnectionService} to create the connection.
+         * @return {@code true} if the call is in a transitory state, {@code false} otherwise.
+         */
+        public boolean isInTransitoryState() {
+            return CallState.isTransitoryState(state)
+                    // Consider it transitory if create connection hasn't completed, EXCEPT if we
+                    // are in SELECT_PHONE_ACCOUNT state since that state will depend on user input.
+                    || (!isCreateConnectionComplete && state != CallState.SELECT_PHONE_ACCOUNT);
+        }
+
+        /**
+         * Determines if the current call is in an intermediate state.  A call is deemed to be in
+         * an intermediate state if either {@link CallState#isIntermediateState(int)} returns true,
+         * AND the call has been created to the connection.
+         * @return {@code true} if the call is in a intermediate state, {@code false} otherwise.
+         */
+        public boolean isInIntermediateState() {
+            return CallState.isIntermediateState(state) && isCreateConnectionComplete;
+        }
+    }
+
+    // Handler for tracking pending timeouts.
+    private final ScheduledExecutorService mScheduledExecutorService;
+    private final TelecomSystem.SyncRoot mLock;
+    private final Timeouts.Adapter mTimeoutAdapter;
+    private final ClockProxy mClockProxy;
+    private AnomalyReporterAdapter mAnomalyReporter = new AnomalyReporterAdapterImpl();
+    // Pre-allocate space for 2 calls; realistically thats all we should ever need (tm)
+    private final Map<Call, ScheduledFuture<?>> mScheduledFutureMap = new ConcurrentHashMap<>(2);
+    private final Map<Call, WatchdogCallState> mWatchdogCallStateMap = new ConcurrentHashMap<>(2);
+    // Track the calls which are pending destruction.
+    // TODO: enhance to handle the case where a call never gets destroyed.
+    private final Set<Call> mCallsPendingDestruction = Collections.newSetFromMap(
+            new ConcurrentHashMap<>(2));
+    private final LocalLog mLocalLog = new LocalLog(20);
+
+    /**
+     * Enables the action to disconnect the call when the Transitory state and Intermediate state
+     * time expires.
+     */
+    private static final String ENABLE_DISCONNECT_CALL_ON_STUCK_STATE =
+            "enable_disconnect_call_on_stuck_state";
+    /**
+     * Anomaly Report UUIDs and corresponding event descriptions specific to CallAnomalyWatchdog.
+     */
+    public static final UUID WATCHDOG_DISCONNECTED_STUCK_CALL_UUID =
+            UUID.fromString("4b093985-c78f-45e3-a9fe-5319f397b025");
+    public static final String WATCHDOG_DISCONNECTED_STUCK_CALL_MSG =
+            "Telecom CallAnomalyWatchdog caught and disconnected a stuck/zombie call.";
+    public static final UUID WATCHDOG_DISCONNECTED_STUCK_EMERGENCY_CALL_UUID =
+            UUID.fromString("d57d8aab-d723-485e-a0dd-d1abb0f346c8");
+    public static final String WATCHDOG_DISCONNECTED_STUCK_EMERGENCY_CALL_MSG =
+            "Telecom CallAnomalyWatchdog caught and disconnected a stuck/zombie emergency call.";
+
+    @VisibleForTesting
+    public void setAnomalyReporterAdapter(AnomalyReporterAdapter mAnomalyReporterAdapter){
+        mAnomalyReporter = mAnomalyReporterAdapter;
+    }
+
+    public CallAnomalyWatchdog(ScheduledExecutorService executorService,
+            TelecomSystem.SyncRoot lock,
+            Timeouts.Adapter timeoutAdapter, ClockProxy clockProxy,
+            EmergencyCallDiagnosticLogger emergencyCallDiagnosticLogger) {
+        mScheduledExecutorService = executorService;
+        mLock = lock;
+        mTimeoutAdapter = timeoutAdapter;
+        mClockProxy = clockProxy;
+        mEmergencyCallDiagnosticLogger = emergencyCallDiagnosticLogger;
+    }
+
+    /**
+     * Start tracking a call that we're waiting for a ConnectionService to create.
+     * @param call the call.
+     */
+    @Override
+    public void onStartCreateConnection(Call call) {
+        maybeTrackCall(call);
+        call.addListener(this);
+    }
+
+    @Override
+    public void onCallAdded(Call call) {
+        maybeTrackCall(call);
+    }
+
+    /**
+     * Override of {@link CallsManagerListenerBase} to track when calls have failed to be created by
+     * a ConnectionService.  These calls should no longer be tracked by the CallAnomalyWatchdog.
+     * @param call the call
+     */
+    @Override
+    public void onCreateConnectionFailed(Call call) {
+        Log.i(this, "onCreateConnectionFailed: call=%s", call.toString());
+        stopTrackingCall(call);
+    }
+
+    /**
+     * Override of {@link CallsManagerListenerBase} to track when calls are removed
+     * @param call the call
+     */
+    @Override
+    public void onCallRemoved(Call call) {
+        Log.i(this, "onCallRemoved: call=%s", call.toString());
+        stopTrackingCall(call);
+    }
+
+    /**
+     * Override of {@link com.android.server.telecom.CallsManager.CallsManagerListener} to track
+     * call state changes.
+     * @param call the call
+     * @param oldState its old state
+     * @param newState the new state
+     */
+    @Override
+    public void onCallStateChanged(Call call, int oldState, int newState) {
+        Log.i(this, "onCallStateChanged: call=%s", call.toString());
+        maybeTrackCall(call);
+    }
+
+    /**
+     * Override of {@link Call.Listener} so we can capture successful creation of calls.
+     * @param call the call
+     * @param callState the state the call is now in
+     */
+    @Override
+    public void onSuccessfulOutgoingCall(Call call, int callState) {
+        maybeTrackCall(call);
+    }
+
+    /**
+     * Override of {@link Call.Listener} so we can capture failed call creation.
+     * @param call the call
+     * @param disconnectCause the disconnect cause
+     */
+    @Override
+    public void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause) {
+        Log.i(this, "onFailedOutgoingCall: call=%s", call.toString());
+        stopTrackingCall(call);
+    }
+
+    /**
+     * Override of {@link Call.Listener} so we can capture successful creation of calls
+     * @param call the call
+     */
+    @Override
+    public void onSuccessfulIncomingCall(Call call) {
+        maybeTrackCall(call);
+    }
+
+    /**
+     * Override of {@link Call.Listener} so we can capture failed call creation.
+     * @param call the call
+     */
+    @Override
+    public void onFailedIncomingCall(Call call) {
+        Log.i(this, "onFailedIncomingCall: call=%s", call.toString());
+        stopTrackingCall(call);
+    }
+
+    /**
+     * Helper method used to stop CallAnomalyWatchdog from tracking or destroying the call.
+     * @param call the call.
+     */
+    private void stopTrackingCall(Call call) {
+        if (mScheduledFutureMap.containsKey(call)) {
+            ScheduledFuture<?> existingTimeout = mScheduledFutureMap.get(call);
+            existingTimeout.cancel(false /* cancelIfRunning */);
+            mScheduledFutureMap.remove(call);
+        }
+        if (mCallsPendingDestruction.contains(call)) {
+            mCallsPendingDestruction.remove(call);
+        }
+        if (mWatchdogCallStateMap.containsKey(call)) {
+            mWatchdogCallStateMap.remove(call);
+        }
+        call.removeListener(this);
+    }
+
+    /**
+     * Given a {@link Call}, potentially post a cleanup task to track when the call has been in a
+     * transitory state too long.
+     * @param call the call.
+     */
+    private void maybeTrackCall(Call call) {
+        final WatchdogCallState currentState = mWatchdogCallStateMap.get(call);
+        final WatchdogCallState newState = new WatchdogCallState(call.getState(),
+                call.isCreateConnectionComplete(), mClockProxy.elapsedRealtime());
+        if (Objects.equals(currentState, newState)) {
+            // No state change; skip.
+            return;
+        }
+        mWatchdogCallStateMap.put(call, newState);
+
+        // The call's state has changed, so we will remove any existing state cleanup tasks.
+        if (mScheduledFutureMap.containsKey(call)) {
+            ScheduledFuture<?> existingTimeout = mScheduledFutureMap.get(call);
+            existingTimeout.cancel(false /* cancelIfRunning */);
+            mScheduledFutureMap.remove(call);
+        }
+
+        Log.i(this, "maybePostCleanupTask; callId=%s, state=%s, createConnComplete=%b",
+                call.getId(), CallState.toString(call.getState()),
+                call.isCreateConnectionComplete());
+
+        long timeoutMillis = getTimeoutMillis(call, newState);
+        boolean isEnabledDisconnect = isEnabledDisconnectForStuckCall();
+        // If the call is now in a transitory or intermediate state, post a new cleanup task.
+        if (timeoutMillis > 0) {
+            Runnable cleanupRunnable = getCleanupRunnable(call, newState, timeoutMillis,
+                    isEnabledDisconnect);
+
+            // Post cleanup to the executor service and cache the future, so we can cancel it if
+            // needed.
+            ScheduledFuture<?> future = mScheduledExecutorService.schedule(cleanupRunnable,
+                    timeoutMillis, TimeUnit.MILLISECONDS);
+            mScheduledFutureMap.put(call, future);
+        }
+    }
+
+    public long getTimeoutMillis(Call call, WatchdogCallState state) {
+        boolean isVoip = call.getIsVoipAudioMode();
+        boolean isEmergency = call.isEmergencyCall();
+
+        if (state.isInTransitoryState()) {
+            if (isVoip) {
+                return (isEmergency) ?
+                        mTimeoutAdapter.getVoipEmergencyCallTransitoryStateTimeoutMillis() :
+                        mTimeoutAdapter.getVoipCallTransitoryStateTimeoutMillis();
+            }
+
+            return (isEmergency) ?
+                    mTimeoutAdapter.getNonVoipEmergencyCallTransitoryStateTimeoutMillis() :
+                    mTimeoutAdapter.getNonVoipCallTransitoryStateTimeoutMillis();
+        }
+
+        if (state.isInIntermediateState()) {
+            if (isVoip) {
+                return (isEmergency) ?
+                        mTimeoutAdapter.getVoipEmergencyCallIntermediateStateTimeoutMillis() :
+                        mTimeoutAdapter.getVoipCallIntermediateStateTimeoutMillis();
+            }
+
+            return (isEmergency) ?
+                    mTimeoutAdapter.getNonVoipEmergencyCallIntermediateStateTimeoutMillis() :
+                    mTimeoutAdapter.getNonVoipCallIntermediateStateTimeoutMillis();
+        }
+
+        return 0;
+    }
+
+    private Runnable getCleanupRunnable(Call call, WatchdogCallState newState, long timeoutMillis,
+            boolean isEnabledDisconnect) {
+        Runnable cleanupRunnable = new android.telecom.Logging.Runnable("CAW.mR", mLock) {
+            @Override
+            public void loggedRun() {
+                // If we're already pending a cleanup due to a state violation for this call.
+                if (mCallsPendingDestruction.contains(call)) {
+                    return;
+                }
+                // Ensure that at timeout we are still in the original state when we posted the
+                // timeout.
+                final WatchdogCallState expiredState = new WatchdogCallState(call.getState(),
+                        call.isCreateConnectionComplete(), mClockProxy.elapsedRealtime());
+                if (expiredState.equals(newState)
+                        && getDurationInCurrentStateMillis(newState) > timeoutMillis) {
+                    // The call has been in this transitory or intermediate state too long,
+                    // so disconnect it and destroy it.
+                    Log.addEvent(call, STATE_TIMEOUT, newState);
+                    mLocalLog.log("STATE_TIMEOUT; callId=" + call.getId() + " in state "
+                            + newState);
+                    if (call.isEmergencyCall()){
+                        mAnomalyReporter.reportAnomaly(
+                                WATCHDOG_DISCONNECTED_STUCK_EMERGENCY_CALL_UUID,
+                                WATCHDOG_DISCONNECTED_STUCK_EMERGENCY_CALL_MSG);
+                        mEmergencyCallDiagnosticLogger.reportStuckCall(call);
+                    } else {
+                        mAnomalyReporter.reportAnomaly(
+                                WATCHDOG_DISCONNECTED_STUCK_CALL_UUID,
+                                WATCHDOG_DISCONNECTED_STUCK_CALL_MSG);
+                    }
+
+                    if (isEnabledDisconnect) {
+                        call.setOverrideDisconnectCauseCode(
+                                new DisconnectCause(DisconnectCause.ERROR, "state_timeout"));
+                        call.disconnect("State timeout");
+                    } else {
+                        writeCallStateChangedAtom(call);
+                    }
+
+                    mCallsPendingDestruction.add(call);
+                    if (mWatchdogCallStateMap.containsKey(call)) {
+                        mWatchdogCallStateMap.remove(call);
+                    }
+                }
+                mScheduledFutureMap.remove(call);
+            }
+        }.prepare();
+        return cleanupRunnable;
+    }
+
+    /**
+     * Returns whether the action to disconnect the call when the Transitory state and
+     * Intermediate state time expires is enabled or disabled.
+     * @return {@code true} if the action is enabled, {@code false} if the action is disabled.
+     */
+    private boolean isEnabledDisconnectForStuckCall() {
+        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_TELEPHONY,
+                ENABLE_DISCONNECT_CALL_ON_STUCK_STATE, false);
+    }
+
+    /**
+     * Determines how long a call has been in a specific state.
+     * @param state the call state.
+     * @return the time in the state, in millis.
+     */
+    private long getDurationInCurrentStateMillis(WatchdogCallState state) {
+        return mClockProxy.elapsedRealtime() - state.stateStartTimeMillis;
+    }
+
+    private void writeCallStateChangedAtom(Call call) {
+        new CallStateChangedAtomWriter()
+                .setDisconnectCause(call.getDisconnectCause())
+                .setSelfManaged(call.isSelfManaged())
+                .setExternalCall(call.isExternalCall())
+                .setEmergencyCall(call.isEmergencyCall())
+                .write(call.getState());
+    }
+
+    /**
+     * Dumps the state of the {@link CallAnomalyWatchdog}.
+     *
+     * @param pw The {@code IndentingPrintWriter} to write the state to.
+     */
+    public void dump(IndentingPrintWriter pw) {
+        pw.println("Anomaly log:");
+        pw.increaseIndent();
+        mLocalLog.dump(pw);
+        pw.decreaseIndent();
+        pw.print("Pending timeouts: ");
+        pw.println(mScheduledFutureMap.keySet().stream().map(c -> c.getId()).collect(
+                Collectors.joining(",")));
+        pw.print("Pending destruction: ");
+        pw.println(mCallsPendingDestruction.stream().map(c -> c.getId()).collect(
+                Collectors.joining(",")));
+    }
+
+    @VisibleForTesting
+    public int getNumberOfScheduledTimeouts() {
+        return mScheduledFutureMap.size();
+    }
+}
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index 1863cde..8cac314 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -17,10 +17,13 @@
 package com.android.server.telecom;
 
 import android.annotation.NonNull;
+import android.content.Context;
 import android.media.IAudioService;
 import android.media.ToneGenerator;
+import android.os.UserHandle;
 import android.telecom.CallAudioState;
 import android.telecom.Log;
+import android.telecom.PhoneAccount;
 import android.telecom.VideoProfile;
 import android.util.SparseArray;
 
@@ -58,6 +61,7 @@
     private final RingbackPlayer mRingbackPlayer;
     private final DtmfLocalTonePlayer mDtmfLocalTonePlayer;
 
+    private Call mStreamingCall;
     private Call mForegroundCall;
     private boolean mIsTonePlaying = false;
     private boolean mIsDisconnectedTonePlaying = false;
@@ -75,6 +79,7 @@
         mRingingCalls = new LinkedHashSet<>(1);
         mHoldingCalls = new LinkedHashSet<>(1);
         mAudioProcessingCalls = new LinkedHashSet<>(1);
+        mStreamingCall = null;
         mCalls = new HashSet<>();
         mCallStateToCalls = new SparseArray<LinkedHashSet<Call>>() {{
             put(CallState.CONNECTING, mActiveDialingOrConnectingCalls);
@@ -219,6 +224,36 @@
     }
 
     /**
+     * Handles the changes to the streaming state of a call.
+     * @param call The call
+     * @param isStreaming {@code true} if the call is streaming, {@code false} otherwise
+     */
+    @Override
+    public void onCallStreamingStateChanged(Call call, boolean isStreaming) {
+        if (isStreaming) {
+            if (mStreamingCall == null) {
+                mStreamingCall = call;
+                mCallAudioModeStateMachine.sendMessageWithArgs(
+                        CallAudioModeStateMachine.START_CALL_STREAMING,
+                        makeArgsForModeStateMachine());
+            } else {
+                Log.w(LOG_TAG, "Unexpected streaming call request for call %s while call "
+                        + "s is streaming.", call.getId(), mStreamingCall.getId());
+            }
+        } else {
+            if (mStreamingCall == call) {
+                mStreamingCall = null;
+                mCallAudioModeStateMachine.sendMessageWithArgs(
+                        CallAudioModeStateMachine.STOP_CALL_STREAMING,
+                        makeArgsForModeStateMachine());
+            } else {
+                Log.w(LOG_TAG, "Unexpected call streaming stop request for call %s while this call "
+                        + "is not streaming.", call.getId());
+            }
+        }
+    }
+
+    /**
      * Determines if {@link CallAudioManager} should do any audio routing operations for a call.
      * We ignore child calls of a conference and external calls for audio routing purposes.
      *
@@ -410,7 +445,8 @@
      * @param bluetoothAddress the address of the desired bluetooth device, if route is
      * {@link CallAudioState#ROUTE_BLUETOOTH}.
      */
-    void setAudioRoute(int route, String bluetoothAddress) {
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
+    public void setAudioRoute(int route, String bluetoothAddress) {
         Log.v(this, "setAudioRoute, route: %s", CallAudioState.audioRouteToString(route));
         switch (route) {
             case CallAudioState.ROUTE_BLUETOOTH:
@@ -450,15 +486,34 @@
                 CallAudioRouteStateMachine.INCLUDE_BLUETOOTH_IN_BASELINE);
     }
 
-    void silenceRingers() {
+    Set<UserHandle> silenceRingers(Context context, UserHandle callingUser,
+            boolean hasCrossUserPermission) {
+        // Store all users from calls that were silenced so that we can silence the
+        // InCallServices which are associated with those users.
+        Set<UserHandle> userHandles = new HashSet<>();
+        boolean allCallSilenced = true;
         synchronized (mCallsManager.getLock()) {
             for (Call call : mRingingCalls) {
+                UserHandle userFromCall = call.getUserHandleFromTargetPhoneAccount();
+                // Do not try to silence calls when calling user is different from the phone account
+                // user, the account does not have CAPABILITY_MULTI_USER enabled, or if the user
+                // does not have the INTERACT_ACROSS_USERS permission enabled.
+                if (!hasCrossUserPermission && !mCallsManager
+                        .isCallVisibleForUser(call, callingUser)) {
+                    allCallSilenced = false;
+                    continue;
+                }
+                userHandles.add(userFromCall);
                 call.silence();
             }
 
-            mRinger.stopRinging();
-            mRinger.stopCallWaiting();
+            // If all the calls were silenced, we can stop the ringer.
+            if (allCallSilenced) {
+                mRinger.stopRinging();
+                mRinger.stopCallWaiting();
+            }
         }
+        return userHandles;
     }
 
     public boolean isRingtonePlaying() {
@@ -737,6 +792,7 @@
                 .setHasHoldingCalls(mHoldingCalls.size() > 0)
                 .setHasAudioProcessingCalls(mAudioProcessingCalls.size() > 0)
                 .setIsTonePlaying(mIsTonePlaying)
+                .setIsStreaming(mStreamingCall != null)
                 .setForegroundCallIsVoip(
                         mForegroundCall != null && isCallVoip(mForegroundCall))
                 .setSession(Log.createSubsession()).build();
diff --git a/src/com/android/server/telecom/CallAudioModeStateMachine.java b/src/com/android/server/telecom/CallAudioModeStateMachine.java
index a1c5f4b..3ced36d 100644
--- a/src/com/android/server/telecom/CallAudioModeStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioModeStateMachine.java
@@ -19,12 +19,12 @@
 import android.media.AudioManager;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Trace;
 import android.telecom.Log;
 import android.telecom.Logging.Runnable;
 import android.telecom.Logging.Session;
 import android.util.LocalLog;
 import android.util.SparseArray;
-
 import com.android.internal.util.IState;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.State;
@@ -50,17 +50,19 @@
         public boolean hasAudioProcessingCalls;
         public boolean isTonePlaying;
         public boolean foregroundCallIsVoip;
+        public boolean isStreaming;
         public Session session;
 
         private MessageArgs(boolean hasActiveOrDialingCalls, boolean hasRingingCalls,
                 boolean hasHoldingCalls, boolean hasAudioProcessingCalls, boolean isTonePlaying,
-                boolean foregroundCallIsVoip, Session session) {
+                boolean foregroundCallIsVoip, boolean isStreaming, Session session) {
             this.hasActiveOrDialingCalls = hasActiveOrDialingCalls;
             this.hasRingingCalls = hasRingingCalls;
             this.hasHoldingCalls = hasHoldingCalls;
             this.hasAudioProcessingCalls = hasAudioProcessingCalls;
             this.isTonePlaying = isTonePlaying;
             this.foregroundCallIsVoip = foregroundCallIsVoip;
+            this.isStreaming = isStreaming;
             this.session = session;
         }
 
@@ -73,6 +75,7 @@
                     ", hasAudioProcessingCalls=" + hasAudioProcessingCalls +
                     ", isTonePlaying=" + isTonePlaying +
                     ", foregroundCallIsVoip=" + foregroundCallIsVoip +
+                    ", isStreaming=" + isStreaming +
                     ", session=" + session +
                     '}';
         }
@@ -84,6 +87,7 @@
             private boolean mHasAudioProcessingCalls;
             private boolean mIsTonePlaying;
             private boolean mForegroundCallIsVoip;
+            private boolean mIsStreaming;
             private Session mSession;
 
             public Builder setHasActiveOrDialingCalls(boolean hasActiveOrDialingCalls) {
@@ -121,9 +125,15 @@
                 return this;
             }
 
+            public Builder setIsStreaming(boolean isStraeming) {
+                mIsStreaming = isStraeming;
+                return this;
+            }
+
             public MessageArgs build() {
                 return new MessageArgs(mHasActiveOrDialingCalls, mHasRingingCalls, mHasHoldingCalls,
-                        mHasAudioProcessingCalls, mIsTonePlaying, mForegroundCallIsVoip, mSession);
+                        mHasAudioProcessingCalls, mIsTonePlaying, mForegroundCallIsVoip,
+                        mIsStreaming, mSession);
             }
         }
     }
@@ -138,7 +148,8 @@
     public static final int ENTER_RING_FOCUS_FOR_TESTING = 4;
     public static final int ENTER_TONE_OR_HOLD_FOCUS_FOR_TESTING = 5;
     public static final int ENTER_AUDIO_PROCESSING_FOCUS_FOR_TESTING = 6;
-    public static final int ABANDON_FOCUS_FOR_TESTING = 7;
+    public static final int ENTER_STREAMING_FOCUS_FOR_TESTING = 7;
+    public static final int ABANDON_FOCUS_FOR_TESTING = 8;
 
     public static final int NO_MORE_ACTIVE_OR_DIALING_CALLS = 1001;
     public static final int NO_MORE_RINGING_CALLS = 1002;
@@ -161,6 +172,9 @@
     // to release focus for other apps to take over.
     public static final int AUDIO_OPERATIONS_COMPLETE = 6001;
 
+    public static final int START_CALL_STREAMING = 7001;
+    public static final int STOP_CALL_STREAMING = 7002;
+
     public static final int RUN_RUNNABLE = 9001;
 
     private static final SparseArray<String> MESSAGE_CODE_TO_NAME = new SparseArray<String>() {{
@@ -183,6 +197,8 @@
         put(FOREGROUND_VOIP_MODE_CHANGE, "FOREGROUND_VOIP_MODE_CHANGE");
         put(RINGER_MODE_CHANGE, "RINGER_MODE_CHANGE");
         put(AUDIO_OPERATIONS_COMPLETE, "AUDIO_OPERATIONS_COMPLETE");
+        put(START_CALL_STREAMING, "START_CALL_STREAMING");
+        put(STOP_CALL_STREAMING, "STOP_CALL_STREAMING");
 
         put(RUN_RUNNABLE, "RUN_RUNNABLE");
     }};
@@ -193,6 +209,7 @@
             AudioProcessingFocusState.class.getSimpleName();
     public static final String CALL_STATE_NAME = SimCallFocusState.class.getSimpleName();
     public static final String RING_STATE_NAME = RingingFocusState.class.getSimpleName();
+    public static final String STREAMING_STATE_NAME = StreamingFocusState.class.getSimpleName();
     public static final String COMMS_STATE_NAME = VoipCallFocusState.class.getSimpleName();
 
     private class BaseState extends State {
@@ -214,6 +231,9 @@
                 case ENTER_AUDIO_PROCESSING_FOCUS_FOR_TESTING:
                     transitionTo(mAudioProcessingFocusState);
                     return HANDLED;
+                case ENTER_STREAMING_FOCUS_FOR_TESTING:
+                    transitionTo(mStreamingFocusState);
+                    return HANDLED;
                 case ABANDON_FOCUS_FOR_TESTING:
                     transitionTo(mUnfocusedState);
                     return HANDLED;
@@ -280,6 +300,9 @@
                             " Args are: \n" + args.toString());
                     transitionTo(mOtherFocusState);
                     return HANDLED;
+                case START_CALL_STREAMING:
+                    transitionTo(mStreamingFocusState);
+                    return HANDLED;
                 case TONE_STARTED_PLAYING:
                     // This shouldn't happen either, but perform the action anyway.
                     Log.w(LOG_TAG, "Tone started playing unexpectedly. Args are: \n"
@@ -353,6 +376,9 @@
                     Log.w(LOG_TAG, "Tone started playing unexpectedly. Args are: \n"
                             + args.toString());
                     return HANDLED;
+                case START_CALL_STREAMING:
+                    transitionTo(mStreamingFocusState);
+                    return HANDLED;
                 case AUDIO_OPERATIONS_COMPLETE:
                     Log.i(LOG_TAG, "Abandoning audio focus: now AUDIO_PROCESSING");
                     mAudioManager.abandonAudioFocusForCall();
@@ -370,26 +396,33 @@
         private boolean mHasFocus = false;
 
         private void tryStartRinging() {
-            if (mHasFocus && mCallAudioManager.isRingtonePlaying()) {
-                Log.i(LOG_TAG, "RingingFocusState#tryStartRinging -- audio focus previously"
-                        + " acquired and ringtone already playing -- skipping.");
-                return;
-            }
-
-            if (mCallAudioManager.startRinging()) {
-                mAudioManager.requestAudioFocusForCall(AudioManager.STREAM_RING,
-                        AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
-                // Do not set MODE_RINGTONE if we were previously in the CALL_SCREENING mode -- this
-                // trips up the audio system.
-                if (mAudioManager.getMode() != AudioManager.MODE_CALL_SCREENING) {
-                    mAudioManager.setMode(AudioManager.MODE_RINGTONE);
-                    mLocalLog.log("Mode MODE_RINGTONE");
+            Trace.traceBegin(Trace.TRACE_TAG_AUDIO, "CallAudioMode.tryStartRinging");
+            try {
+                if (mHasFocus && mCallAudioManager.isRingtonePlaying()) {
+                    Log.i(LOG_TAG,
+                        "RingingFocusState#tryStartRinging -- audio focus previously"
+                            + " acquired and ringtone already playing -- skipping.");
+                    return;
                 }
-                mCallAudioManager.setCallAudioRouteFocusState(
+
+                if (mCallAudioManager.startRinging()) {
+                    mAudioManager.requestAudioFocusForCall(
+                        AudioManager.STREAM_RING, AudioManager.AUDIOFOCUS_GAIN_TRANSIENT);
+                    // Do not set MODE_RINGTONE if we were previously in the CALL_SCREENING mode --
+                    // this trips up the audio system.
+                    if (mAudioManager.getMode() != AudioManager.MODE_CALL_SCREENING) {
+                        mAudioManager.setMode(AudioManager.MODE_RINGTONE);
+                        mLocalLog.log("Mode MODE_RINGTONE");
+                    }
+                    mCallAudioManager.setCallAudioRouteFocusState(
                         CallAudioRouteStateMachine.RINGING_FOCUS);
-                mHasFocus = true;
-            } else {
-                Log.i(LOG_TAG, "RINGING state, try start ringing but not acquiring audio focus");
+                    mHasFocus = true;
+                } else {
+                    Log.i(
+                        LOG_TAG, "RINGING state, try start ringing but not acquiring audio focus");
+                }
+            } finally {
+                Trace.traceEnd(Trace.TRACE_TAG_AUDIO);
             }
         }
 
@@ -618,6 +651,79 @@
                     Log.w(LOG_TAG, "Should not be seeing AUDIO_OPERATIONS_COMPLETE in a focused"
                             + " state");
                     return HANDLED;
+                case START_CALL_STREAMING:
+                    transitionTo(mStreamingFocusState);
+                    return HANDLED;
+                default:
+                    // The forced focus switch commands are handled by BaseState.
+                    return NOT_HANDLED;
+            }
+        }
+    }
+
+    private class StreamingFocusState extends BaseState {
+        @Override
+        public void enter() {
+            Log.i(LOG_TAG, "Audio focus entering streaming state");
+            mAudioManager.setMode(AudioManager.MODE_CALL_REDIRECT);
+            mMostRecentMode = AudioManager.MODE_NORMAL;
+            mCallAudioManager.setCallAudioRouteFocusState(CallAudioRouteStateMachine.ACTIVE_FOCUS);
+            mCallAudioManager.getCallAudioRouteStateMachine().sendMessageWithSessionInfo(
+                    CallAudioRouteStateMachine.STREAMING_FORCE_ENABLED);
+        }
+
+        private void preExit() {
+            mCallAudioManager.getCallAudioRouteStateMachine().sendMessageWithSessionInfo(
+                    CallAudioRouteStateMachine.STREAMING_FORCE_DISABLED);
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (super.processMessage(msg) == HANDLED) {
+                return HANDLED;
+            }
+            MessageArgs args = (MessageArgs) msg.obj;
+            switch (msg.what) {
+                case NO_MORE_ACTIVE_OR_DIALING_CALLS:
+                    // Do nothing.
+                    return HANDLED;
+                case NO_MORE_RINGING_CALLS:
+                    // Do nothing.
+                    return HANDLED;
+                case NO_MORE_HOLDING_CALLS:
+                    // Do nothing.
+                    return HANDLED;
+                case NO_MORE_AUDIO_PROCESSING_CALLS:
+                    // Do nothing.
+                    return HANDLED;
+                case NEW_ACTIVE_OR_DIALING_CALL:
+                    // Only possible for emergency call
+                    BaseState destState = calculateProperStateFromArgs(args);
+                    if (destState != this) {
+                        preExit();
+                        transitionTo(destState);
+                    }
+                    return HANDLED;
+                case NEW_RINGING_CALL:
+                    // Only possible for emergency call
+                    preExit();
+                    transitionTo(mRingingFocusState);
+                    return HANDLED;
+                case NEW_HOLDING_CALL:
+                    // Do nothing.
+                    return HANDLED;
+                case NEW_AUDIO_PROCESSING_CALL:
+                    // Do nothing.
+                    return HANDLED;
+                case START_CALL_STREAMING:
+                    // Can happen as a duplicate message
+                    return HANDLED;
+                case TONE_STARTED_PLAYING:
+                    // Do nothing.
+                    return HANDLED;
+                case STOP_CALL_STREAMING:
+                    transitionTo(calculateProperStateFromArgs(args));
+                    return HANDLED;
                 default:
                     // The forced focus switch commands are handled by BaseState.
                     return NOT_HANDLED;
@@ -700,6 +806,7 @@
     private final BaseState mSimCallFocusState = new SimCallFocusState();
     private final BaseState mVoipCallFocusState = new VoipCallFocusState();
     private final BaseState mAudioProcessingFocusState = new AudioProcessingFocusState();
+    private final BaseState mStreamingFocusState = new StreamingFocusState();
     private final BaseState mOtherFocusState = new OtherFocusState();
 
     private final AudioManager mAudioManager;
@@ -738,6 +845,7 @@
         addState(mSimCallFocusState);
         addState(mVoipCallFocusState);
         addState(mAudioProcessingFocusState);
+        addState(mStreamingFocusState);
         addState(mOtherFocusState);
         setInitialState(mUnfocusedState);
         start();
@@ -747,6 +855,7 @@
                 .setHasHoldingCalls(false)
                 .setIsTonePlaying(false)
                 .setForegroundCallIsVoip(false)
+                .setIsStreaming(false)
                 .setSession(Log.createSubsession())
                 .build());
     }
@@ -800,12 +909,15 @@
         // switch to the appropriate focus.
         // Otherwise abandon focus.
 
-        // The order matters here. If there are active calls, holding focus for them takes priority.
-        // After that, we want to prioritize holding calls over ringing calls so that when a
-        // call-waiting call gets answered, there's no transition in and out of the ringing focus
-        // state. After that, we want tones since we actually hold focus during them, then the
-        // audio processing state because that will release focus.
-        if (args.hasActiveOrDialingCalls) {
+        // The order matters here. If there is streaming call, holding streaming route for them
+        // takes priority. After that, holding focus for active calls takes priority. After that, we
+        // want to prioritize holding calls over ringing calls so that when a call-waiting call gets
+        // answered, there's no transition in and out of the ringing focus state. After that, we
+        // want tones since we actually hold focus during them, then the audio processing state
+        // because that will release focus.
+        if (args.isStreaming) {
+            return mSimCallFocusState;
+        } else if (args.hasActiveOrDialingCalls) {
             if (args.foregroundCallIsVoip) {
                 return mVoipCallFocusState;
             } else {
diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
index 2ed7d95..531f42e 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -48,6 +48,8 @@
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.Executor;
 
 /**
  * This class describes the available routes of a call as a state machine.
@@ -80,14 +82,16 @@
                 WiredHeadsetManager wiredHeadsetManager,
                 StatusBarNotifier statusBarNotifier,
                 CallAudioManager.AudioServiceFactory audioServiceFactory,
-                int earpieceControl) {
+                int earpieceControl,
+                Executor asyncTaskExecutor) {
             return new CallAudioRouteStateMachine(context,
                     callsManager,
                     bluetoothManager,
                     wiredHeadsetManager,
                     statusBarNotifier,
                     audioServiceFactory,
-                    earpieceControl);
+                    earpieceControl,
+                    asyncTaskExecutor);
         }
     }
     /** Values for CallAudioRouteStateMachine constructor's earPieceRouting arg. */
@@ -107,6 +111,9 @@
     /** Direct the audio stream through the device's speakerphone. */
     public static final int ROUTE_SPEAKER       = CallAudioState.ROUTE_SPEAKER;
 
+    /** Direct the audio stream through another device. */
+    public static final int ROUTE_STREAMING     = CallAudioState.ROUTE_STREAMING;
+
     /** Valid values for msg.what */
     public static final int CONNECT_WIRED_HEADSET = 1;
     public static final int DISCONNECT_WIRED_HEADSET = 2;
@@ -128,6 +135,10 @@
     public static final int SPEAKER_ON = 1006;
     public static final int SPEAKER_OFF = 1007;
 
+    // Messages denoting that the streaming route switch request was sent.
+    public static final int STREAMING_FORCE_ENABLED = 1008;
+    public static final int STREAMING_FORCE_DISABLED = 1009;
+
     public static final int USER_SWITCH_EARPIECE = 1101;
     public static final int USER_SWITCH_BLUETOOTH = 1102;
     public static final int USER_SWITCH_HEADSET = 1103;
@@ -544,6 +555,9 @@
                 case DISCONNECT_DOCK:
                     // Nothing to do here
                     return HANDLED;
+                case STREAMING_FORCE_ENABLED:
+                    transitionTo(mStreamingState);
+                    return HANDLED;
                 default:
                     return NOT_HANDLED;
             }
@@ -628,6 +642,9 @@
                         mCallAudioManager.notifyAudioOperationsComplete();
                     }
                     return HANDLED;
+                case STREAMING_FORCE_ENABLED:
+                    transitionTo(mStreamingState);
+                    return HANDLED;
                 default:
                     return NOT_HANDLED;
             }
@@ -1105,6 +1122,9 @@
                 case DISCONNECT_DOCK:
                     // Nothing to do here
                     return HANDLED;
+                case STREAMING_FORCE_ENABLED:
+                    transitionTo(mStreamingState);
+                    return HANDLED;
                 default:
                     return NOT_HANDLED;
             }
@@ -1330,12 +1350,74 @@
                 case DISCONNECT_DOCK:
                     sendInternalMessage(SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE);
                     return HANDLED;
+                case STREAMING_FORCE_ENABLED:
+                    transitionTo(mStreamingState);
+                    return HANDLED;
                default:
                     return NOT_HANDLED;
             }
         }
     }
 
+    class StreamingState extends AudioState {
+        @Override
+        public void enter() {
+            super.enter();
+            updateSystemAudioState();
+        }
+
+        @Override
+        public void updateSystemAudioState() {
+            updateInternalCallAudioState();
+            setSystemAudioState(mCurrentCallAudioState);
+        }
+
+        @Override
+        public boolean isActive() {
+            return true;
+        }
+
+        @Override
+        public int getRouteCode() {
+            return CallAudioState.ROUTE_STREAMING;
+        }
+
+        @Override
+        public boolean processMessage(Message msg) {
+            if (super.processMessage(msg) == HANDLED) {
+                return HANDLED;
+            }
+            switch (msg.what) {
+                case SWITCH_EARPIECE:
+                case USER_SWITCH_EARPIECE:
+                case SPEAKER_OFF:
+                    // Nothing to do here
+                    return HANDLED;
+                case SPEAKER_ON:
+                    // fall through
+                case BT_AUDIO_CONNECTED:
+                case SWITCH_BLUETOOTH:
+                case USER_SWITCH_BLUETOOTH:
+                case SWITCH_HEADSET:
+                case USER_SWITCH_HEADSET:
+                case SWITCH_SPEAKER:
+                case USER_SWITCH_SPEAKER:
+                    return HANDLED;
+                case SWITCH_FOCUS:
+                    if (msg.arg1 == NO_FOCUS) {
+                        reinitialize();
+                        mCallAudioManager.notifyAudioOperationsComplete();
+                    }
+                    return HANDLED;
+                case STREAMING_FORCE_DISABLED:
+                    reinitialize();
+                    return HANDLED;
+                default:
+                    return NOT_HANDLED;
+            }
+        }
+    }
+
     private final BroadcastReceiver mMuteChangeReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -1398,6 +1480,9 @@
     private final QuiescentHeadsetRoute mQuiescentHeadsetRoute = new QuiescentHeadsetRoute();
     private final QuiescentBluetoothRoute mQuiescentBluetoothRoute = new QuiescentBluetoothRoute();
     private final QuiescentSpeakerRoute mQuiescentSpeakerRoute = new QuiescentSpeakerRoute();
+    private final StreamingState mStreamingState = new StreamingState();
+
+    private final Executor mAsyncTaskExecutor;
 
     /**
      * A few pieces of hidden state. Used to avoid exponential explosion of number of explicit
@@ -1437,7 +1522,8 @@
             WiredHeadsetManager wiredHeadsetManager,
             StatusBarNotifier statusBarNotifier,
             CallAudioManager.AudioServiceFactory audioServiceFactory,
-            int earpieceControl) {
+            int earpieceControl,
+            Executor asyncTaskExecutor) {
         super(NAME);
         mContext = context;
         mCallsManager = callsManager;
@@ -1447,7 +1533,7 @@
         mStatusBarNotifier = statusBarNotifier;
         mAudioServiceFactory = audioServiceFactory;
         mLock = callsManager.getLock();
-
+        mAsyncTaskExecutor = asyncTaskExecutor;
         createStates(earpieceControl);
     }
 
@@ -1459,7 +1545,7 @@
             WiredHeadsetManager wiredHeadsetManager,
             StatusBarNotifier statusBarNotifier,
             CallAudioManager.AudioServiceFactory audioServiceFactory,
-            int earpieceControl, Looper looper) {
+            int earpieceControl, Looper looper, Executor asyncTaskExecutor) {
         super(NAME, looper);
         mContext = context;
         mCallsManager = callsManager;
@@ -1469,6 +1555,7 @@
         mStatusBarNotifier = statusBarNotifier;
         mAudioServiceFactory = audioServiceFactory;
         mLock = callsManager.getLock();
+        mAsyncTaskExecutor = asyncTaskExecutor;
 
         createStates(earpieceControl);
     }
@@ -1494,6 +1581,7 @@
         addState(mQuiescentHeadsetRoute);
         addState(mQuiescentBluetoothRoute);
         addState(mQuiescentSpeakerRoute);
+        addState(mStreamingState);
 
 
         mStateNameToRouteCode = new HashMap<>(8);
@@ -1506,12 +1594,14 @@
         mStateNameToRouteCode.put(mActiveBluetoothRoute.getName(), ROUTE_BLUETOOTH);
         mStateNameToRouteCode.put(mActiveHeadsetRoute.getName(), ROUTE_WIRED_HEADSET);
         mStateNameToRouteCode.put(mActiveSpeakerRoute.getName(), ROUTE_SPEAKER);
+        mStateNameToRouteCode.put(mStreamingState.getName(), ROUTE_STREAMING);
 
         mRouteCodeToQuiescentState = new HashMap<>(4);
         mRouteCodeToQuiescentState.put(ROUTE_EARPIECE, mQuiescentEarpieceRoute);
         mRouteCodeToQuiescentState.put(ROUTE_BLUETOOTH, mQuiescentBluetoothRoute);
         mRouteCodeToQuiescentState.put(ROUTE_SPEAKER, mQuiescentSpeakerRoute);
         mRouteCodeToQuiescentState.put(ROUTE_WIRED_HEADSET, mQuiescentHeadsetRoute);
+        mRouteCodeToQuiescentState.put(ROUTE_STREAMING, mStreamingState);
     }
 
     public void setCallAudioManager(CallAudioManager callAudioManager) {
@@ -1547,7 +1637,8 @@
                 new IntentFilter(AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED));
 
         mStatusBarNotifier.notifyMute(initState.isMuted());
-        mStatusBarNotifier.notifySpeakerphone(initState.getRoute() == CallAudioState.ROUTE_SPEAKER);
+        // We used to call mStatusBarNotifier.notifySpeakerphone, but that makes no sense as there
+        // is never a call at this boot (init) time.
         setInitialState(mRouteCodeToQuiescentState.get(initState.getRoute()));
         start();
     }
@@ -1640,26 +1731,32 @@
 
     private void setSpeakerphoneOn(boolean on) {
         Log.i(this, "turning speaker phone %s", on);
-        AudioDeviceInfo speakerDevice = null;
-        for (AudioDeviceInfo info : mAudioManager.getAvailableCommunicationDevices()) {
-            if (info.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
-                speakerDevice = info;
-                break;
+        final boolean hasAnyCalls = mCallsManager.hasAnyCalls();
+        // These APIs are all via two-way binder calls so can potentially block Telecom.  Since none
+        // of this has to happen in the Telecom lock we'll offload it to the async executor.
+        mAsyncTaskExecutor.execute(() -> {
+            AudioDeviceInfo speakerDevice = null;
+            for (AudioDeviceInfo info : mAudioManager.getAvailableCommunicationDevices()) {
+                if (info.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
+                    speakerDevice = info;
+                    break;
+                }
             }
-        }
-        boolean speakerOn = false;
-        if (speakerDevice != null && on) {
-            boolean result = mAudioManager.setCommunicationDevice(speakerDevice);
-            if (result) {
-                speakerOn = true;
+            boolean speakerOn = false;
+            if (speakerDevice != null && on) {
+                boolean result = mAudioManager.setCommunicationDevice(speakerDevice);
+                if (result) {
+                    speakerOn = true;
+                }
+            } else {
+                AudioDeviceInfo curDevice = mAudioManager.getCommunicationDevice();
+                if (curDevice != null
+                        && curDevice.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
+                    mAudioManager.clearCommunicationDevice();
+                }
             }
-        } else {
-            AudioDeviceInfo curDevice = mAudioManager.getCommunicationDevice();
-            if (curDevice != null && curDevice.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
-                mAudioManager.clearCommunicationDevice();
-            }
-        }
-        mStatusBarNotifier.notifySpeakerphone(speakerOn);
+            mStatusBarNotifier.notifySpeakerphone(hasAnyCalls && speakerOn);
+        });
     }
 
     private void setBluetoothOn(String address) {
@@ -1764,16 +1861,18 @@
             if (force || !newCallAudioState.equals(mLastKnownCallAudioState)) {
                 mStatusBarNotifier.notifyMute(newCallAudioState.isMuted());
                 mCallsManager.onCallAudioStateChanged(mLastKnownCallAudioState, newCallAudioState);
-                updateAudioForForegroundCall(newCallAudioState);
+                updateAudioStateForTrackedCalls(newCallAudioState);
                 mLastKnownCallAudioState = newCallAudioState;
             }
         }
     }
 
-    private void updateAudioForForegroundCall(CallAudioState newCallAudioState) {
-        Call call = mCallsManager.getForegroundCall();
-        if (call != null && call.getConnectionService() != null) {
-            call.getConnectionService().onCallAudioStateChanged(call, newCallAudioState);
+    private void updateAudioStateForTrackedCalls(CallAudioState newCallAudioState) {
+        Set<Call> calls = mCallsManager.getTrackedCalls();
+        for (Call call : calls) {
+            if (call != null && call.getConnectionService() != null) {
+                call.getConnectionService().onCallAudioStateChanged(call, newCallAudioState);
+            }
         }
     }
 
diff --git a/src/com/android/server/telecom/CallDiagnosticServiceController.java b/src/com/android/server/telecom/CallDiagnosticServiceController.java
index 7bd7288..6c7ee38 100644
--- a/src/com/android/server/telecom/CallDiagnosticServiceController.java
+++ b/src/com/android/server/telecom/CallDiagnosticServiceController.java
@@ -25,7 +25,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
-import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.os.Bundle;
 import android.os.IBinder;
@@ -86,17 +85,19 @@
          * Listens for changes to extras reported by a Telecom {@link Call}.
          *
          * Extras changes can originate from a {@link ConnectionService} or an {@link InCallService}
-         * so we will only trigger an update of the call information if the source of the extras
-         * change was a {@link ConnectionService}.
+         * so we will only trigger an update of the call information if the source of the
+         * extras change was a {@link ConnectionService}.
          *
-         * @param call The call.
-         * @param source The source of the extras change ({@link Call#SOURCE_CONNECTION_SERVICE} or
+         * @param call   The call.
+         * @param source The source of the extras change
+         *               ({@link Call#SOURCE_CONNECTION_SERVICE} or
          *               {@link Call#SOURCE_INCALL_SERVICE}).
          * @param extras The extras.
          */
         @Override
-        public void onExtrasChanged(Call call, int source, Bundle extras) {
-            // Do not inform InCallServices of changes which originated there.
+        public void onExtrasChanged(Call call, int source, Bundle extras,
+                String requestingPackageName) {
+            // Do not inform of changes which originated from an InCallService to a CDS.
             if (source == Call.SOURCE_INCALL_SERVICE) {
                 return;
             }
diff --git a/src/com/android/server/telecom/CallEndpointController.java b/src/com/android/server/telecom/CallEndpointController.java
new file mode 100644
index 0000000..60827e2
--- /dev/null
+++ b/src/com/android/server/telecom/CallEndpointController.java
@@ -0,0 +1,372 @@
+/*
+ * Copyright 2022, 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.server.telecom;
+
+import android.content.Context;
+import android.bluetooth.BluetoothDevice;
+import android.os.Bundle;
+import android.os.ParcelUuid;
+import android.os.ResultReceiver;
+import android.telecom.CallAudioState;
+import android.telecom.CallEndpoint;
+import android.telecom.CallEndpointException;
+import android.telecom.Log;
+import com.android.internal.annotations.VisibleForTesting;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Provides to {@link CallsManager} the service that can request change of CallEndpoint to the
+ * {@link CallAudioManager}. And notify change of CallEndpoint status to {@link CallsManager}
+ */
+public class CallEndpointController extends CallsManagerListenerBase {
+    public static final int CHANGE_TIMEOUT_SEC = 2;
+    public static final int RESULT_REQUEST_SUCCESS = 0;
+    public static final int RESULT_ENDPOINT_DOES_NOT_EXIST = 1;
+    public static final int RESULT_REQUEST_TIME_OUT = 2;
+    public static final int RESULT_ANOTHER_REQUEST = 3;
+    public static final int RESULT_UNSPECIFIED_ERROR = 4;
+
+    private final Context mContext;
+    private final CallsManager mCallsManager;
+    private final HashMap<Integer, Integer> mRouteToTypeMap;
+    private final HashMap<Integer, Integer> mTypeToRouteMap;
+    private final Map<ParcelUuid, String> mBluetoothAddressMap = new HashMap<>();
+    private final Set<CallEndpoint> mAvailableCallEndpoints = new HashSet<>();
+    private CallEndpoint mActiveCallEndpoint;
+    private ParcelUuid mRequestedEndpointId;
+    private CompletableFuture<Integer> mPendingChangeRequest;
+
+    public CallEndpointController(Context context, CallsManager callsManager) {
+        mContext = context;
+        mCallsManager = callsManager;
+
+        mRouteToTypeMap = new HashMap<>(5);
+        mRouteToTypeMap.put(CallAudioState.ROUTE_EARPIECE, CallEndpoint.TYPE_EARPIECE);
+        mRouteToTypeMap.put(CallAudioState.ROUTE_BLUETOOTH, CallEndpoint.TYPE_BLUETOOTH);
+        mRouteToTypeMap.put(CallAudioState.ROUTE_WIRED_HEADSET, CallEndpoint.TYPE_WIRED_HEADSET);
+        mRouteToTypeMap.put(CallAudioState.ROUTE_SPEAKER, CallEndpoint.TYPE_SPEAKER);
+        mRouteToTypeMap.put(CallAudioState.ROUTE_STREAMING, CallEndpoint.TYPE_STREAMING);
+
+        mTypeToRouteMap = new HashMap<>(5);
+        mTypeToRouteMap.put(CallEndpoint.TYPE_EARPIECE, CallAudioState.ROUTE_EARPIECE);
+        mTypeToRouteMap.put(CallEndpoint.TYPE_BLUETOOTH, CallAudioState.ROUTE_BLUETOOTH);
+        mTypeToRouteMap.put(CallEndpoint.TYPE_WIRED_HEADSET, CallAudioState.ROUTE_WIRED_HEADSET);
+        mTypeToRouteMap.put(CallEndpoint.TYPE_SPEAKER, CallAudioState.ROUTE_SPEAKER);
+        mTypeToRouteMap.put(CallEndpoint.TYPE_STREAMING, CallAudioState.ROUTE_STREAMING);
+    }
+
+    @VisibleForTesting
+    public CallEndpoint getCurrentCallEndpoint() {
+        return mActiveCallEndpoint;
+    }
+
+    @VisibleForTesting
+    public Set<CallEndpoint> getAvailableEndpoints() {
+        return mAvailableCallEndpoints;
+    }
+
+    public void requestCallEndpointChange(CallEndpoint endpoint, ResultReceiver callback) {
+        Log.d(this, "requestCallEndpointChange %s", endpoint);
+        int route = mTypeToRouteMap.get(endpoint.getEndpointType());
+        String bluetoothAddress = getBluetoothAddress(endpoint);
+
+        if (findMatchingTypeEndpoint(endpoint.getEndpointType()) == null ||
+                (route == CallAudioState.ROUTE_BLUETOOTH && bluetoothAddress == null)) {
+            callback.send(CallEndpoint.ENDPOINT_OPERATION_FAILED,
+                    getErrorResult(RESULT_ENDPOINT_DOES_NOT_EXIST));
+            return;
+        }
+
+        if (mPendingChangeRequest != null && !mPendingChangeRequest.isDone()) {
+            mPendingChangeRequest.complete(RESULT_ANOTHER_REQUEST);
+            mPendingChangeRequest = null;
+            mRequestedEndpointId = null;
+        }
+
+        mPendingChangeRequest = new CompletableFuture<Integer>()
+                .completeOnTimeout(RESULT_REQUEST_TIME_OUT, CHANGE_TIMEOUT_SEC, TimeUnit.SECONDS);
+
+        mPendingChangeRequest.thenAcceptAsync((result) -> {
+            if (result == RESULT_REQUEST_SUCCESS) {
+                callback.send(CallEndpoint.ENDPOINT_OPERATION_SUCCESS, new Bundle());
+            } else {
+                callback.send(CallEndpoint.ENDPOINT_OPERATION_FAILED, getErrorResult(result));
+            }
+        });
+        mRequestedEndpointId = endpoint.getIdentifier();
+        mCallsManager.getCallAudioManager().setAudioRoute(route, bluetoothAddress);
+    }
+
+    private Bundle getErrorResult(int result) {
+        String message;
+        int resultCode;
+        switch (result) {
+            case RESULT_ENDPOINT_DOES_NOT_EXIST:
+                message = "Requested CallEndpoint does not exist";
+                resultCode = CallEndpointException.ERROR_ENDPOINT_DOES_NOT_EXIST;
+                break;
+            case RESULT_REQUEST_TIME_OUT:
+                message = "The operation was not completed on time";
+                resultCode = CallEndpointException.ERROR_REQUEST_TIME_OUT;
+                break;
+            case RESULT_ANOTHER_REQUEST:
+                message = "The operation was canceled by another request";
+                resultCode = CallEndpointException.ERROR_ANOTHER_REQUEST;
+                break;
+            default:
+                message = "The operation has failed due to an unknown or unspecified error";
+                resultCode = CallEndpointException.ERROR_UNSPECIFIED;
+        }
+        CallEndpointException exception = new CallEndpointException(message, resultCode);
+        Bundle extras = new Bundle();
+        extras.putParcelable(CallEndpointException.CHANGE_ERROR, exception);
+        return extras;
+    }
+
+    @VisibleForTesting
+    public String getBluetoothAddress(CallEndpoint endpoint) {
+        return mBluetoothAddressMap.get(endpoint.getIdentifier());
+    }
+
+    private void notifyCallEndpointChange() {
+        if (mActiveCallEndpoint == null) {
+            Log.i(this, "notifyCallEndpointChange, invalid CallEndpoint");
+            return;
+        }
+
+        if (mRequestedEndpointId != null && mPendingChangeRequest != null &&
+                mRequestedEndpointId.equals(mActiveCallEndpoint.getIdentifier())) {
+            mPendingChangeRequest.complete(RESULT_REQUEST_SUCCESS);
+            mPendingChangeRequest = null;
+            mRequestedEndpointId = null;
+        }
+        mCallsManager.updateCallEndpoint(mActiveCallEndpoint);
+
+        Set<Call> calls = mCallsManager.getTrackedCalls();
+        for (Call call : calls) {
+            if (call != null && call.getConnectionService() != null) {
+                call.getConnectionService().onCallEndpointChanged(call, mActiveCallEndpoint);
+            }
+            else if (call != null && call.getTransactionServiceWrapper() != null) {
+                call.getTransactionServiceWrapper()
+                        .onCallEndpointChanged(call, mActiveCallEndpoint);
+            }
+        }
+    }
+
+    private void notifyAvailableCallEndpointsChange() {
+        mCallsManager.updateAvailableCallEndpoints(mAvailableCallEndpoints);
+
+        Set<Call> calls = mCallsManager.getTrackedCalls();
+        for (Call call : calls) {
+            if (call != null && call.getConnectionService() != null) {
+                call.getConnectionService().onAvailableCallEndpointsChanged(call,
+                        mAvailableCallEndpoints);
+            }
+            else if (call != null && call.getTransactionServiceWrapper() != null) {
+                call.getTransactionServiceWrapper()
+                        .onAvailableCallEndpointsChanged(call, mAvailableCallEndpoints);
+            }
+        }
+    }
+
+    private void notifyMuteStateChange(boolean isMuted) {
+        mCallsManager.updateMuteState(isMuted);
+
+        Set<Call> calls = mCallsManager.getTrackedCalls();
+        for (Call call : calls) {
+            if (call != null && call.getConnectionService() != null) {
+                call.getConnectionService().onMuteStateChanged(call, isMuted);
+            }
+            else if (call != null && call.getTransactionServiceWrapper() != null) {
+                call.getTransactionServiceWrapper().onMuteStateChanged(call, isMuted);
+            }
+        }
+    }
+
+    private void createAvailableCallEndpoints(CallAudioState state) {
+        Set<CallEndpoint> newAvailableEndpoints = new HashSet<>();
+        Map<ParcelUuid, String> newBluetoothDevices = new HashMap<>();
+
+        mRouteToTypeMap.forEach((route, type)->{
+            if ((state.getSupportedRouteMask() & route) != 0) {
+                if (type == CallEndpoint.TYPE_STREAMING) {
+                    if (state.getRoute() == CallAudioState.ROUTE_STREAMING) {
+                        if (mActiveCallEndpoint == null
+                                || mActiveCallEndpoint.getEndpointType() != type) {
+                            mActiveCallEndpoint = new CallEndpoint(getEndpointName(type) != null
+                                    ? getEndpointName(type) : "", type);
+                        }
+                    }
+                } else if (type == CallEndpoint.TYPE_BLUETOOTH) {
+                    for (BluetoothDevice device : state.getSupportedBluetoothDevices()) {
+                        CallEndpoint endpoint = findMatchingBluetoothEndpoint(device);
+                        if (endpoint == null) {
+                            endpoint = new CallEndpoint(
+                                    device.getName() != null ? device.getName() : "",
+                                    CallEndpoint.TYPE_BLUETOOTH);
+                        }
+                        newAvailableEndpoints.add(endpoint);
+                        newBluetoothDevices.put(endpoint.getIdentifier(), device.getAddress());
+
+                        BluetoothDevice activeDevice = state.getActiveBluetoothDevice();
+                        if (state.getRoute() == route && device.equals(activeDevice)) {
+                            mActiveCallEndpoint = endpoint;
+                        }
+                    }
+                } else {
+                    CallEndpoint endpoint = findMatchingTypeEndpoint(type);
+                    if (endpoint == null) {
+                        endpoint = new CallEndpoint(
+                                getEndpointName(type) != null ? getEndpointName(type) : "", type);
+                    }
+                    newAvailableEndpoints.add(endpoint);
+                    if (state.getRoute() == route) {
+                        mActiveCallEndpoint = endpoint;
+                    }
+                }
+            }
+        });
+        mAvailableCallEndpoints.clear();
+        mAvailableCallEndpoints.addAll(newAvailableEndpoints);
+        mBluetoothAddressMap.clear();
+        mBluetoothAddressMap.putAll(newBluetoothDevices);
+    }
+
+    private CallEndpoint findMatchingTypeEndpoint(int targetType) {
+        for (CallEndpoint endpoint : mAvailableCallEndpoints) {
+            if (endpoint.getEndpointType() == targetType) {
+                return endpoint;
+            }
+        }
+        return null;
+    }
+
+    private CallEndpoint findMatchingBluetoothEndpoint(BluetoothDevice device) {
+        final String targetAddress = device.getAddress();
+        if (targetAddress != null) {
+            for (CallEndpoint endpoint : mAvailableCallEndpoints) {
+                final String address = mBluetoothAddressMap.get(endpoint.getIdentifier());
+                if (targetAddress.equals(address)) {
+                    return endpoint;
+                }
+            }
+        }
+        return null;
+    }
+
+    private boolean isAvailableEndpointChanged(CallAudioState oldState, CallAudioState newState) {
+        if (oldState == null) {
+            return true;
+        }
+        if ((oldState.getSupportedRouteMask() ^ newState.getSupportedRouteMask()) != 0) {
+            return true;
+        }
+        if (oldState.getSupportedBluetoothDevices().size() !=
+                newState.getSupportedBluetoothDevices().size()) {
+            return true;
+        }
+        for (BluetoothDevice device : newState.getSupportedBluetoothDevices()) {
+            if (!oldState.getSupportedBluetoothDevices().contains(device)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private boolean isEndpointChanged(CallAudioState oldState, CallAudioState newState) {
+        if (oldState == null) {
+            return true;
+        }
+        if (oldState.getRoute() != newState.getRoute()) {
+            return true;
+        }
+        if (newState.getRoute() == CallAudioState.ROUTE_BLUETOOTH) {
+            if (oldState.getActiveBluetoothDevice() == null) {
+                if (newState.getActiveBluetoothDevice() == null) {
+                    return false;
+                }
+                return true;
+            }
+            return !oldState.getActiveBluetoothDevice().equals(newState.getActiveBluetoothDevice());
+        }
+        return false;
+    }
+
+    private boolean isMuteStateChanged(CallAudioState oldState, CallAudioState newState) {
+        if (oldState == null) {
+            return true;
+        }
+        return oldState.isMuted() != newState.isMuted();
+    }
+
+    private CharSequence getEndpointName(int endpointType) {
+        switch (endpointType) {
+            case CallEndpoint.TYPE_EARPIECE:
+                return mContext.getText(R.string.callendpoint_name_earpiece);
+            case CallEndpoint.TYPE_BLUETOOTH:
+                return mContext.getText(R.string.callendpoint_name_bluetooth);
+            case CallEndpoint.TYPE_WIRED_HEADSET:
+                return mContext.getText(R.string.callendpoint_name_wiredheadset);
+            case CallEndpoint.TYPE_SPEAKER:
+                return mContext.getText(R.string.callendpoint_name_speaker);
+            case CallEndpoint.TYPE_STREAMING:
+                return mContext.getText(R.string.callendpoint_name_streaming);
+            default:
+                return mContext.getText(R.string.callendpoint_name_unknown);
+        }
+    }
+
+    @Override
+    public void onCallAudioStateChanged(CallAudioState oldState, CallAudioState newState) {
+        Log.i(this, "onCallAudioStateChanged, audioState: %s -> %s", oldState, newState);
+
+        if (newState == null) {
+            Log.i(this, "onCallAudioStateChanged, invalid audioState");
+            return;
+        }
+
+        createAvailableCallEndpoints(newState);
+
+        boolean isforce = true;
+        if (isAvailableEndpointChanged(oldState, newState)) {
+            notifyAvailableCallEndpointsChange();
+            isforce = false;
+        }
+
+        if (isEndpointChanged(oldState, newState)) {
+            notifyCallEndpointChange();
+            isforce = false;
+        }
+
+        if (isMuteStateChanged(oldState, newState)) {
+            notifyMuteStateChange(newState.isMuted());
+            isforce = false;
+        }
+
+        if (isforce) {
+            notifyAvailableCallEndpointsChange();
+            notifyCallEndpointChange();
+            notifyMuteStateChange(newState.isMuted());
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/CallEndpointControllerFactory.java b/src/com/android/server/telecom/CallEndpointControllerFactory.java
new file mode 100644
index 0000000..a9b03c3
--- /dev/null
+++ b/src/com/android/server/telecom/CallEndpointControllerFactory.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2022, 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.server.telecom;
+
+import android.content.Context;
+
+/**
+ * Abstracts out creation of CallEndpointController for unit test purposes.
+ */
+public interface CallEndpointControllerFactory {
+    CallEndpointController create(Context context, TelecomSystem.SyncRoot lock,
+            CallsManager callsManager);
+}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/CallLogManager.java b/src/com/android/server/telecom/CallLogManager.java
old mode 100755
new mode 100644
index 0ec2362..a7bbf84
--- a/src/com/android/server/telecom/CallLogManager.java
+++ b/src/com/android/server/telecom/CallLogManager.java
@@ -386,7 +386,12 @@
         if (okayToLog) {
             AddCallArgs args = new AddCallArgs(mContext, paramBuilder.build(),
                     logCallCompletedListener);
+            Log.addEvent(call, LogUtils.Events.LOG_CALL, "number=" + Log.piiHandle(logNumber)
+                    + ",postDial=" + Log.piiHandle(call.getPostDialDigits()) + ",pres="
+                    + call.getHandlePresentation());
             logCallAsync(args);
+        } else {
+            Log.addEvent(call, LogUtils.Events.SKIP_CALL_LOG);
         }
     }
 
diff --git a/src/com/android/server/telecom/CallScreeningServiceHelper.java b/src/com/android/server/telecom/CallScreeningServiceHelper.java
index 0168590..9426100 100644
--- a/src/com/android/server/telecom/CallScreeningServiceHelper.java
+++ b/src/com/android/server/telecom/CallScreeningServiceHelper.java
@@ -229,8 +229,9 @@
                 serviceConnection,
                 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
                 | Context.BIND_SCHEDULE_LIKE_TOP_APP,
-                UserHandle.CURRENT)) {
-            Log.d(TAG, "bindService, found service, waiting for it to connect");
+                userHandle)) {
+            Log.d(TAG,"bindServiceAsUser, found service,"
+                    + "waiting for it to connect to user: %s", userHandle);
             return true;
         }
 
diff --git a/src/com/android/server/telecom/CallState.java b/src/com/android/server/telecom/CallState.java
index 0411ecc..c35c7ec 100644
--- a/src/com/android/server/telecom/CallState.java
+++ b/src/com/android/server/telecom/CallState.java
@@ -132,6 +132,46 @@
      */
     public static final int SIMULATED_RINGING = TelecomProtoEnums.SIMULATED_RINGING; // = 13
 
+    /**
+     * Determines if a given call state is "transitory".  A transitory call state is one which a
+     * call should only be in for a short duration of time before lower levels move it to a more
+     * permanent stable state.
+     *
+     * It is tempting to consider {@link #DIALING}, for example, to be transitory, however the time
+     * spent in this state is entirely dependent on how long the call is actually in that state and
+     * it is expected a call can be {@link #DIALING} for potentially a minute or more.
+     * @param callState the state to check
+     * @return {@code true} if the state is transitory, {@code false} otherwise.
+     */
+    public static boolean isTransitoryState(int callState) {
+        switch (callState) {
+            case NEW:
+            case CONNECTING:
+            case DISCONNECTING:
+            case ANSWERED:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Determines if a given call state is "intermediate".  An intermediate call state may exist
+     * before a stable call state, but may be longer than a transitory state.
+     *
+     * @param callState the state to check
+     * @return {@code true} if the state is intermediate, {@code false} otherwise.
+     */
+    public static boolean isIntermediateState(int callState) {
+        switch (callState) {
+            case DIALING:
+            case RINGING:
+                return true;
+            default:
+                return false;
+        }
+    }
+
     public static String toString(int callState) {
         switch (callState) {
             case NEW:
diff --git a/src/com/android/server/telecom/CallStreamingController.java b/src/com/android/server/telecom/CallStreamingController.java
new file mode 100644
index 0000000..31b2235
--- /dev/null
+++ b/src/com/android/server/telecom/CallStreamingController.java
@@ -0,0 +1,395 @@
+/*
+ * Copyright (C) 2022 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.server.telecom;
+
+import static android.telecom.CallStreamingService.STREAMING_FAILED_SENDER_BINDING_ERROR;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.app.role.RoleManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.OutcomeReceiver;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.telecom.CallException;
+import android.telecom.CallStreamingService;
+import android.telecom.StreamingCall;
+import android.util.Log;
+
+import com.android.internal.telecom.ICallStreamingService;
+import com.android.server.telecom.voip.VoipCallTransaction;
+import com.android.server.telecom.voip.VoipCallTransactionResult;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+
+public class CallStreamingController extends CallsManagerListenerBase {
+    private Call mStreamingCall;
+    private TransactionalServiceWrapper mTransactionalServiceWrapper;
+    private ICallStreamingService mService;
+    private final Context mContext;
+    private CallStreamingServiceConnection mConnection;
+    private boolean mIsStreaming;
+    private final Object mLock;
+    private TelecomSystem.SyncRoot mTelecomLock;
+
+    public CallStreamingController(Context context, TelecomSystem.SyncRoot telecomLock) {
+        mLock = new Object();
+        mContext = context;
+        mTelecomLock = telecomLock;
+    }
+
+    private void onConnectedInternal(Call call, TransactionalServiceWrapper wrapper,
+            IBinder service) throws RemoteException {
+        synchronized (mLock) {
+            mStreamingCall = call;
+            mTransactionalServiceWrapper = wrapper;
+            mService = ICallStreamingService.Stub.asInterface(service);
+            mService.setStreamingCallAdapter(new StreamingCallAdapter(mTransactionalServiceWrapper,
+                    mStreamingCall,
+                    mStreamingCall.getTargetPhoneAccount().getComponentName().getPackageName()));
+            mService.onCallStreamingStarted(new StreamingCall(
+                    mTransactionalServiceWrapper.getComponentName(),
+                    mStreamingCall.getCallerDisplayName(),
+                    mStreamingCall.getContactUri(), new Bundle()));
+            mIsStreaming = true;
+        }
+    }
+
+    private void resetController() {
+        synchronized (mLock) {
+            mStreamingCall = null;
+            mTransactionalServiceWrapper = null;
+            if (mConnection != null) {
+                mContext.unbindService(mConnection);
+                mConnection = null;
+            }
+            mService = null;
+            mIsStreaming = false;
+        }
+    }
+
+    public boolean isStreaming() {
+        synchronized (mLock) {
+            return mIsStreaming;
+        }
+    }
+
+    public static class QueryCallStreamingTransaction extends VoipCallTransaction {
+        private static final String TAG = QueryCallStreamingTransaction.class.getSimpleName();
+        private final CallsManager mCallsManager;
+
+        public QueryCallStreamingTransaction(CallsManager callsManager) {
+            super(callsManager.getLock());
+            mCallsManager = callsManager;
+        }
+
+        @Override
+        public CompletableFuture<VoipCallTransactionResult> processTransaction(Void v) {
+            Log.d(TAG, "processTransaction");
+            CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
+
+            if (mCallsManager.getCallStreamingController().isStreaming()) {
+                future.complete(new VoipCallTransactionResult(
+                        VoipCallTransactionResult.RESULT_FAILED,
+                        "STREAMING_FAILED_ALREADY_STREAMING"));
+            } else {
+                future.complete(new VoipCallTransactionResult(
+                        VoipCallTransactionResult.RESULT_SUCCEED, null));
+            }
+
+            return future;
+        }
+    }
+
+    public static class AudioInterceptionTransaction extends VoipCallTransaction {
+        private static final String TAG = AudioInterceptionTransaction.class.getSimpleName();
+
+        private Call mCall;
+        private boolean mEnterInterception;
+
+        public AudioInterceptionTransaction(Call call, boolean enterInterception,
+                TelecomSystem.SyncRoot lock) {
+            super(lock);
+            mCall = call;
+            mEnterInterception = enterInterception;
+        }
+
+        @Override
+        public CompletableFuture<VoipCallTransactionResult> processTransaction(Void v) {
+            Log.d(TAG, "processTransaction");
+            CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
+
+            if (mEnterInterception) {
+                mCall.startStreaming();
+            } else {
+                mCall.stopStreaming();
+            }
+            future.complete(new VoipCallTransactionResult(VoipCallTransactionResult.RESULT_SUCCEED,
+                    null));
+            return future;
+        }
+    }
+
+    public StreamingServiceTransaction getCallStreamingServiceTransaction(Context context,
+            TransactionalServiceWrapper wrapper, Call call) {
+        return new StreamingServiceTransaction(context, wrapper, call);
+    }
+
+    public class StreamingServiceTransaction extends VoipCallTransaction {
+        private static final String TAG = "StreamingServiceTransaction";
+        public static final String MESSAGE = "STREAMING_FAILED_NO_SENDER";
+        private final TransactionalServiceWrapper mWrapper;
+        private final Context mContext;
+        private final UserHandle mUserHandle;
+        private final Call mCall;
+
+        public StreamingServiceTransaction(Context context, TransactionalServiceWrapper wrapper,
+                Call call) {
+            super(mTelecomLock);
+            mWrapper = wrapper;
+            mCall = call;
+            mUserHandle = mCall.getInitiatingUser();
+            mContext = context;
+        }
+
+        @SuppressLint("LongLogTag")
+        @Override
+        public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+            Log.d(TAG, "processTransaction");
+            CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
+
+            RoleManager roleManager = mContext.getSystemService(RoleManager.class);
+            PackageManager packageManager = mContext.getPackageManager();
+            if (roleManager == null || packageManager == null) {
+                Log.e(TAG, "Can't find system service");
+                future.complete(new VoipCallTransactionResult(
+                        VoipCallTransactionResult.RESULT_FAILED, MESSAGE));
+                return future;
+            }
+
+            List<String> holders = roleManager.getRoleHoldersAsUser(
+                    RoleManager.ROLE_SYSTEM_CALL_STREAMING, mUserHandle);
+            if (holders.isEmpty()) {
+                Log.e(TAG, "Can't find streaming app");
+                future.complete(new VoipCallTransactionResult(
+                        VoipCallTransactionResult.RESULT_FAILED, MESSAGE));
+                return future;
+            }
+
+            Intent serviceIntent = new Intent(CallStreamingService.SERVICE_INTERFACE);
+            serviceIntent.setPackage(holders.get(0));
+            List<ResolveInfo> infos = packageManager.queryIntentServicesAsUser(serviceIntent,
+                    PackageManager.GET_META_DATA, mUserHandle);
+            if (infos.isEmpty()) {
+                Log.e(TAG, "Can't find streaming service");
+                future.complete(new VoipCallTransactionResult(
+                        VoipCallTransactionResult.RESULT_FAILED, MESSAGE));
+                return future;
+            }
+
+            ServiceInfo serviceInfo = infos.get(0).serviceInfo;
+
+            if (serviceInfo.permission == null || !serviceInfo.permission.equals(
+                    Manifest.permission.BIND_CALL_STREAMING_SERVICE)) {
+                android.telecom.Log.w(TAG, "Must require BIND_CALL_STREAMING_SERVICE: " +
+                        serviceInfo.packageName);
+                future.complete(new VoipCallTransactionResult(
+                        VoipCallTransactionResult.RESULT_FAILED, MESSAGE));
+                return future;
+            }
+            Intent intent = new Intent(CallStreamingService.SERVICE_INTERFACE);
+            intent.setComponent(serviceInfo.getComponentName());
+
+            mConnection =  new CallStreamingServiceConnection(mCall, mWrapper, future);
+            if (!mContext.bindServiceAsUser(intent, mConnection, Context.BIND_AUTO_CREATE
+                    | Context.BIND_FOREGROUND_SERVICE
+                    | Context.BIND_SCHEDULE_LIKE_TOP_APP, mUserHandle)) {
+                Log.e(TAG, "Can't bind to streaming service");
+                future.complete(new VoipCallTransactionResult(
+                        VoipCallTransactionResult.RESULT_FAILED,
+                        "STREAMING_FAILED_SENDER_BINDING_ERROR"));
+            }
+
+            return future;
+        }
+    }
+
+    public UnbindStreamingServiceTransaction getUnbindStreamingServiceTransaction() {
+        return new UnbindStreamingServiceTransaction();
+    }
+
+    public class UnbindStreamingServiceTransaction extends VoipCallTransaction {
+        private static final String TAG = "UnbindStreamingServiceTransaction";
+
+        public UnbindStreamingServiceTransaction() {
+            super(mTelecomLock);
+        }
+
+        @SuppressLint("LongLogTag")
+        @Override
+        public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+            Log.d(TAG, "processTransaction");
+            CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
+
+            resetController();
+            future.complete(new VoipCallTransactionResult(VoipCallTransactionResult.RESULT_SUCCEED,
+                    null));
+            return future;
+        }
+    }
+
+    @Override
+    public void onCallRemoved(Call call) {
+        if (mStreamingCall == call) {
+            mTransactionalServiceWrapper.stopCallStreaming(call);
+        }
+    }
+
+    @Override
+    public void onCallStateChanged(Call call, int oldState, int newState) {
+        // TODO: make sure we are only able to stream the one call and not switch focus to another
+        // and have it streamed too
+        if (mStreamingCall == call && oldState != newState) {
+            CallStreamingStateChangeTransaction transaction = null;
+            switch (newState) {
+                case CallState.ACTIVE:
+                    transaction = new CallStreamingStateChangeTransaction(
+                            StreamingCall.STATE_STREAMING);
+                    break;
+                case CallState.ON_HOLD:
+                    transaction = new CallStreamingStateChangeTransaction(
+                            StreamingCall.STATE_HOLDING);
+                case CallState.DISCONNECTING:
+                case CallState.DISCONNECTED:
+                    transaction = new CallStreamingStateChangeTransaction(
+                            StreamingCall.STATE_DISCONNECTED);
+                default:
+                    // ignore
+            }
+            if (transaction != null) {
+                mTransactionalServiceWrapper.getTransactionManager().addTransaction(transaction,
+                        new OutcomeReceiver<>() {
+                            @Override
+                            public void onResult(VoipCallTransactionResult result) {
+                                // ignore
+                            }
+
+                            @Override
+                            public void onError(CallException exception) {
+                                Log.e(String.valueOf(this), "Exception when set call "
+                                        + "streaming state to streaming app: " + exception);
+                            }
+                        });
+            }
+        }
+    }
+
+    private class CallStreamingStateChangeTransaction extends VoipCallTransaction {
+        @StreamingCall.StreamingCallState int mState;
+
+        public CallStreamingStateChangeTransaction(@StreamingCall.StreamingCallState int state) {
+            super(mTelecomLock);
+            mState = state;
+        }
+
+        @Override
+        public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+            CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
+            try {
+                mService.onCallStreamingStateChanged(mState);
+                future.complete(new VoipCallTransactionResult(
+                        VoipCallTransactionResult.RESULT_SUCCEED, null));
+            } catch (RemoteException e) {
+                future.complete(new VoipCallTransactionResult(
+                        VoipCallTransactionResult.RESULT_FAILED, "Exception when request "
+                        + "setting state to streaming app."));
+            }
+            return future;
+        }
+    }
+
+    private class CallStreamingServiceConnection implements
+            ServiceConnection {
+        private Call mCall;
+        private TransactionalServiceWrapper mWrapper;
+        private CompletableFuture<VoipCallTransactionResult> mFuture;
+
+        public CallStreamingServiceConnection(Call call, TransactionalServiceWrapper wrapper,
+                CompletableFuture<VoipCallTransactionResult> future) {
+            mCall = call;
+            mWrapper = wrapper;
+            mFuture = future;
+        }
+
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            try {
+                onConnectedInternal(mCall, mWrapper, service);
+                mFuture.complete(new VoipCallTransactionResult(
+                        VoipCallTransactionResult.RESULT_SUCCEED, null));
+            } catch (RemoteException e) {
+                resetController();
+                mFuture.complete(new VoipCallTransactionResult(
+                        VoipCallTransactionResult.RESULT_FAILED,
+                        StreamingServiceTransaction.MESSAGE));
+            }
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            clearBinding();
+        }
+
+        @Override
+        public void onBindingDied(ComponentName name) {
+            clearBinding();
+        }
+
+        @Override
+        public void onNullBinding(ComponentName name) {
+            clearBinding();
+        }
+
+        private void clearBinding() {
+            try {
+                if (mService != null) {
+                    mService.onCallStreamingStopped();
+                }
+            } catch (RemoteException e) {
+                Log.w(String.valueOf(this), "Exception when stop call streaming:" + e);
+            }
+            resetController();
+            if (!mFuture.isDone()) {
+                mFuture.complete(new VoipCallTransactionResult(
+                        VoipCallTransactionResult.RESULT_FAILED,
+                        "STREAMING_FAILED_SENDER_BINDING_ERROR"));
+            } else {
+                mWrapper.onCallStreamingFailed(mCall, STREAMING_FAILED_SENDER_BINDING_ERROR);
+            }
+        }
+    }
+}
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
old mode 100755
new mode 100644
index 3fce799..e195fd2
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -16,8 +16,13 @@
 
 package com.android.server.telecom;
 
+import static android.provider.CallLog.Calls.AUTO_MISSED_EMERGENCY_CALL;
+import static android.provider.CallLog.Calls.AUTO_MISSED_MAXIMUM_DIALING;
+import static android.provider.CallLog.Calls.AUTO_MISSED_MAXIMUM_RINGING;
 import static android.provider.CallLog.Calls.MISSED_REASON_NOT_MISSED;
 import static android.provider.CallLog.Calls.SHORT_RING_THRESHOLD;
+import static android.provider.CallLog.Calls.USER_MISSED_CALL_FILTERS_TIMEOUT;
+import static android.provider.CallLog.Calls.USER_MISSED_CALL_SCREENING_SERVICE_SILENCED;
 import static android.provider.CallLog.Calls.USER_MISSED_NEVER_RANG;
 import static android.provider.CallLog.Calls.USER_MISSED_NO_ANSWER;
 import static android.provider.CallLog.Calls.USER_MISSED_SHORT_RING;
@@ -32,17 +37,13 @@
 import static android.telecom.TelecomManager.MEDIUM_CALL_TIME_MS;
 import static android.telecom.TelecomManager.SHORT_CALL_TIME_MS;
 import static android.telecom.TelecomManager.VERY_SHORT_CALL_TIME_MS;
-import static android.provider.CallLog.Calls.AUTO_MISSED_EMERGENCY_CALL;
-import static android.provider.CallLog.Calls.AUTO_MISSED_MAXIMUM_DIALING;
-import static android.provider.CallLog.Calls.AUTO_MISSED_MAXIMUM_RINGING;
-import static android.provider.CallLog.Calls.USER_MISSED_CALL_FILTERS_TIMEOUT;
-import static android.provider.CallLog.Calls.USER_MISSED_CALL_SCREENING_SERVICE_SILENCED;
 
 import android.Manifest;
 import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.AlertDialog;
 import android.app.KeyguardManager;
+import android.app.NotificationManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -50,6 +51,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.ResolveInfoFlags;
 import android.content.pm.UserInfo;
 import android.graphics.Color;
 import android.graphics.drawable.ColorDrawable;
@@ -58,13 +60,14 @@
 import android.media.MediaPlayer;
 import android.media.ToneGenerator;
 import android.net.Uri;
-import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
+import android.os.OutcomeReceiver;
 import android.os.PersistableBundle;
 import android.os.Process;
+import android.os.ResultReceiver;
 import android.os.SystemClock;
 import android.os.SystemVibrator;
 import android.os.Trace;
@@ -73,9 +76,13 @@
 import android.provider.BlockedNumberContract;
 import android.provider.BlockedNumberContract.SystemContract;
 import android.provider.CallLog.Calls;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.sysprop.TelephonyProperties;
+import android.telecom.CallAttributes;
 import android.telecom.CallAudioState;
+import android.telecom.CallEndpoint;
+import android.telecom.CallException;
 import android.telecom.CallScreeningService;
 import android.telecom.CallerInfo;
 import android.telecom.Conference;
@@ -93,7 +100,9 @@
 import android.telecom.TelecomManager;
 import android.telecom.VideoProfile;
 import android.telephony.CarrierConfigManager;
+import android.telephony.CellIdentity;
 import android.telephony.PhoneNumberUtils;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.Pair;
@@ -108,22 +117,26 @@
 import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
 import com.android.server.telecom.callfiltering.BlockCheckerAdapter;
 import com.android.server.telecom.callfiltering.BlockCheckerFilter;
+import com.android.server.telecom.callfiltering.BlockedNumbersAdapter;
 import com.android.server.telecom.callfiltering.CallFilterResultCallback;
 import com.android.server.telecom.callfiltering.CallFilteringResult;
 import com.android.server.telecom.callfiltering.CallFilteringResult.Builder;
 import com.android.server.telecom.callfiltering.CallScreeningServiceFilter;
 import com.android.server.telecom.callfiltering.DirectToVoicemailFilter;
+import com.android.server.telecom.callfiltering.DndCallFilter;
 import com.android.server.telecom.callfiltering.IncomingCallFilterGraph;
 import com.android.server.telecom.callredirection.CallRedirectionProcessor;
 import com.android.server.telecom.components.ErrorDialogActivity;
 import com.android.server.telecom.components.TelecomBroadcastReceiver;
-import com.android.server.telecom.settings.BlockedNumbersUtil;
+import com.android.server.telecom.stats.CallFailureCause;
 import com.android.server.telecom.ui.AudioProcessingNotification;
 import com.android.server.telecom.ui.CallRedirectionTimeoutDialogActivity;
 import com.android.server.telecom.ui.ConfirmCallDialogActivity;
 import com.android.server.telecom.ui.DisconnectedCallNotifier;
 import com.android.server.telecom.ui.IncomingCallNotifier;
 import com.android.server.telecom.ui.ToastFactory;
+import com.android.server.telecom.voip.TransactionManager;
+import com.android.server.telecom.voip.VoipCallMonitor;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -138,10 +151,12 @@
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
+import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
@@ -155,14 +170,20 @@
  * access from other packages specifically refraining from passing the CallsManager instance
  * beyond the com.android.server.telecom package boundary.
  */
-@VisibleForTesting
 public class CallsManager extends Call.ListenerBase
         implements VideoProviderProxy.Listener, CallFilterResultCallback, CurrentUserProxy {
 
     // TODO: Consider renaming this CallsManagerPlugin.
     @VisibleForTesting
     public interface CallsManagerListener {
+        /**
+         * Informs listeners when a {@link Call} is newly created, but not yet returned by a
+         * {@link android.telecom.ConnectionService} implementation.
+         * @param call the call.
+         */
+        default void onStartCreateConnection(Call call) {}
         void onCallAdded(Call call);
+        void onCreateConnectionFailed(Call call);
         void onCallRemoved(Call call);
         void onCallStateChanged(Call call, int oldState, int newState);
         void onConnectionServiceChanged(
@@ -172,6 +193,9 @@
         void onIncomingCallAnswered(Call call);
         void onIncomingCallRejected(Call call, boolean rejectWithMessage, String textMessage);
         void onCallAudioStateChanged(CallAudioState oldAudioState, CallAudioState newAudioState);
+        void onCallEndpointChanged(CallEndpoint callEndpoint);
+        void onAvailableCallEndpointsChanged(Set<CallEndpoint> availableCallEndpoints);
+        void onMuteStateChanged(boolean isMuted);
         void onRingbackRequested(Call call, boolean ringback);
         void onIsConferencedChanged(Call call);
         void onIsVoipAudioModeChanged(Call call);
@@ -180,6 +204,7 @@
         void onSessionModifyRequestReceived(Call call, VideoProfile videoProfile);
         void onHoldToneRequested(Call call);
         void onExternalCallChanged(Call call, boolean isExternalCall);
+        void onCallStreamingStateChanged(Call call, boolean isStreaming);
         void onDisconnectedTonePlaying(boolean isTonePlaying);
         void onConnectionTimeChanged(Call call);
         void onConferenceStateChanged(Call call, boolean isConference);
@@ -227,6 +252,45 @@
     private static final int MAXIMUM_TOP_LEVEL_CALLS = 2;
     private static final int MAXIMUM_SELF_MANAGED_CALLS = 10;
 
+    /**
+     * Anomaly Report UUIDs and corresponding error descriptions specific to CallsManager.
+     */
+    public static final UUID LIVE_CALL_STUCK_CONNECTING_ERROR_UUID =
+            UUID.fromString("3f95808c-9134-11ed-a1eb-0242ac120002");
+    public static final String LIVE_CALL_STUCK_CONNECTING_ERROR_MSG =
+            "Force disconnected a live call that was stuck in CONNECTING state.";
+    public static final UUID LIVE_CALL_STUCK_CONNECTING_EMERGENCY_ERROR_UUID =
+            UUID.fromString("744fdf86-9137-11ed-a1eb-0242ac120002");
+    public static final String LIVE_CALL_STUCK_CONNECTING_EMERGENCY_ERROR_MSG =
+            "Found a live call that was stuck in CONNECTING state while attempting to place an "
+                    + "emergency call.";
+    public static final UUID CALL_REMOVAL_EXECUTION_ERROR_UUID =
+            UUID.fromString("030b8b16-9139-11ed-a1eb-0242ac120002");
+    public static final String CALL_REMOVAL_EXECUTION_ERROR_MSG =
+            "Exception thrown while executing call removal";
+    public static final UUID EXCEPTION_WHILE_ESTABLISHING_CONNECTION_ERROR_UUID =
+            UUID.fromString("1c4eed7c-9132-11ed-a1eb-0242ac120002");
+    public static final String EXCEPTION_WHILE_ESTABLISHING_CONNECTION_ERROR_MSG =
+            "Exception thrown while establishing connection.";
+    public static final UUID EXCEPTION_RETRIEVING_PHONE_ACCOUNTS_ERROR_UUID =
+            UUID.fromString("b68c881d-0ed8-4f31-9342-8bf416c96d18");
+    public static final String EXCEPTION_RETRIEVING_PHONE_ACCOUNTS_ERROR_MSG =
+            "Exception thrown while retrieving list of potential phone accounts.";
+    public static final UUID EXCEPTION_RETRIEVING_PHONE_ACCOUNTS_EMERGENCY_ERROR_UUID =
+            UUID.fromString("f272f89d-fb3a-4004-aa2d-20b8d679467e");
+    public static final String EXCEPTION_RETRIEVING_PHONE_ACCOUNTS_EMERGENCY_ERROR_MSG =
+            "Exception thrown while retrieving list of potential phone accounts when placing an "
+                    + "emergency call.";
+    public static final UUID EMERGENCY_CALL_DISCONNECTED_BEFORE_BEING_ADDED_ERROR_UUID =
+            UUID.fromString("f9a916c8-8d61-4550-9ad3-11c2e84f6364");
+    public static final String EMERGENCY_CALL_DISCONNECTED_BEFORE_BEING_ADDED_ERROR_MSG =
+            "An emergency call was disconnected after the connection was created but before the "
+                    + "call was successfully added to CallsManager.";
+    public static final UUID EMERGENCY_CALL_ABORTED_NO_PHONE_ACCOUNTS_ERROR_UUID =
+            UUID.fromString("2e994acb-1997-4345-8bf3-bad04303de26");
+    public static final String EMERGENCY_CALL_ABORTED_NO_PHONE_ACCOUNTS_ERROR_MSG =
+            "An emergency call was aborted since there were no available phone accounts.";
+
     private static final int[] OUTGOING_CALL_STATES =
             {CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING,
                     CallState.PULLING};
@@ -374,6 +438,14 @@
     private final Handler mHandler = new Handler(Looper.getMainLooper());
     private final EmergencyCallHelper mEmergencyCallHelper;
     private final RoleManagerAdapter mRoleManagerAdapter;
+    private final VoipCallMonitor mVoipCallMonitor;
+    private final CallEndpointController mCallEndpointController;
+    private final CallAnomalyWatchdog mCallAnomalyWatchdog;
+
+    private final EmergencyCallDiagnosticLogger mEmergencyCallDiagnosticLogger;
+    private final CallStreamingController mCallStreamingController;
+    private final BlockedNumbersAdapter mBlockedNumbersAdapter;
+    private final TransactionManager mTransactionManager;
 
     private final ConnectionServiceFocusManager.CallsManagerRequester mRequester =
             new ConnectionServiceFocusManager.CallsManagerRequester() {
@@ -394,14 +466,17 @@
 
     private boolean mCanAddCall = true;
 
-    private int mMaxNumberOfSimultaneouslyActiveSims = -1;
-
     private Runnable mStopTone;
 
     private LinkedList<HandlerThread> mGraphHandlerThreads;
 
+    // An executor that can be used to fire off async tasks that do not block Telecom in any manner.
+    private final Executor mAsyncTaskExecutor;
+
     private boolean mHasActiveRttCall = false;
 
+    private AnomalyReporterAdapter mAnomalyReporter = new AnomalyReporterAdapterImpl();
+
     /**
      * Listener to PhoneAccountRegistrar events.
      */
@@ -432,34 +507,14 @@
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            Log.startSession("CM.CCCR");
             String action = intent.getAction();
             if (CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED.equals(action)
                     || SystemContract.ACTION_BLOCK_SUPPRESSION_STATE_CHANGED.equals(action)) {
-                new UpdateEmergencyCallNotificationTask().doInBackground(
-                        Pair.create(context, Log.createSubsession()));
+                updateEmergencyCallNotificationAsync(context);
             }
         }
     };
 
-    private static class UpdateEmergencyCallNotificationTask
-            extends AsyncTask<Pair<Context, Session>, Void, Void> {
-        @SafeVarargs
-        @Override
-        protected final Void doInBackground(Pair<Context, Session>... args) {
-            if (args == null || args.length != 1 || args[0] == null) {
-                Log.e(this, new IllegalArgumentException(), "Incorrect invocation");
-                return null;
-            }
-            Log.continueSession(args[0].second, "CM.UECNT");
-            Context context = args[0].first;
-            BlockedNumbersUtil.updateEmergencyCallNotification(context,
-                    SystemContract.shouldShowEmergencyCallNotification(context));
-            Log.endSession();
-            return null;
-        }
-    }
-
     /**
      * Initializes the required Telecom components.
      */
@@ -494,7 +549,15 @@
             InCallControllerFactory inCallControllerFactory,
             CallDiagnosticServiceController callDiagnosticServiceController,
             RoleManagerAdapter roleManagerAdapter,
-            ToastFactory toastFactory) {
+            ToastFactory toastFactory,
+            CallEndpointControllerFactory callEndpointControllerFactory,
+            CallAnomalyWatchdog callAnomalyWatchdog,
+            Ringer.AccessibilityManagerAdapter accessibilityManagerAdapter,
+            Executor asyncTaskExecutor,
+            BlockedNumbersAdapter blockedNumbersAdapter,
+            TransactionManager transactionManager,
+            EmergencyCallDiagnosticLogger emergencyCallDiagnosticLogger) {
+
         mContext = context;
         mLock = lock;
         mPhoneNumberUtilsAdapter = phoneNumberUtilsAdapter;
@@ -511,6 +574,7 @@
         mTimeoutsAdapter = timeoutsAdapter;
         mEmergencyCallHelper = emergencyCallHelper;
         mCallerInfoLookupHelper = callerInfoLookupHelper;
+        mEmergencyCallDiagnosticLogger = emergencyCallDiagnosticLogger;
 
         mDtmfLocalTonePlayer =
                 new DtmfLocalTonePlayer(new DtmfLocalTonePlayer.ToneGeneratorProxy());
@@ -522,7 +586,8 @@
                         wiredHeadsetManager,
                         statusBarNotifier,
                         audioServiceFactory,
-                        CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT
+                        CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT,
+                        asyncTaskExecutor
                 );
         callAudioRouteStateMachine.initialize();
 
@@ -532,7 +597,6 @@
                         bluetoothManager,
                         wiredHeadsetManager,
                         mDockManager);
-
         AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
         InCallTonePlayer.MediaPlayerFactory mediaPlayerFactory =
                 (resourceId, attributes) ->
@@ -549,11 +613,14 @@
         mInCallController = inCallControllerFactory.create(context, mLock, this,
                 systemStateHelper, defaultDialerCache, mTimeoutsAdapter,
                 emergencyCallHelper);
+        mCallEndpointController = callEndpointControllerFactory.create(context, mLock, this);
         mCallDiagnosticServiceController = callDiagnosticServiceController;
         mCallDiagnosticServiceController.setInCallTonePlayerFactory(playerFactory);
         mRinger = new Ringer(playerFactory, context, systemSettingsUtil, asyncRingtonePlayer,
                 ringtoneFactory, systemVibrator,
-                new Ringer.VibrationEffectProxy(), mInCallController);
+                new Ringer.VibrationEffectProxy(), mInCallController,
+                mContext.getSystemService(NotificationManager.class),
+                accessibilityManagerAdapter);
         mCallRecordingTonePlayer = new CallRecordingTonePlayer(mContext, audioManager,
                 mTimeoutsAdapter, mLock);
         mCallAudioManager = new CallAudioManager(callAudioRouteStateMachine,
@@ -574,11 +641,16 @@
         mClockProxy = clockProxy;
         mToastFactory = toastFactory;
         mRoleManagerAdapter = roleManagerAdapter;
+        mTransactionManager = transactionManager;
+        mBlockedNumbersAdapter = blockedNumbersAdapter;
+        mCallStreamingController = new CallStreamingController(mContext, mLock);
+        mVoipCallMonitor = new VoipCallMonitor(mContext, mLock);
 
         mListeners.add(mInCallWakeLockController);
         mListeners.add(statusBarNotifier);
         mListeners.add(mCallLogManager);
         mListeners.add(mInCallController);
+        mListeners.add(mCallEndpointController);
         mListeners.add(mCallDiagnosticServiceController);
         mListeners.add(mCallAudioManager);
         mListeners.add(mCallRecordingTonePlayer);
@@ -587,9 +659,15 @@
         mListeners.add(mHeadsetMediaButton);
         mListeners.add(mProximitySensorManager);
         mListeners.add(audioProcessingNotification);
+        mListeners.add(callAnomalyWatchdog);
+        mListeners.add(mEmergencyCallDiagnosticLogger);
+        mListeners.add(mCallStreamingController);
 
         // this needs to be after the mCallAudioManager
         mListeners.add(mPhoneStateBroadcaster);
+        mListeners.add(mVoipCallMonitor);
+
+        mVoipCallMonitor.startMonitor();
 
         // There is no USER_SWITCHED broadcast for user 0, handle it here explicitly.
         final UserManager userManager = UserManager.get(mContext);
@@ -603,6 +681,9 @@
         intentFilter.addAction(SystemContract.ACTION_BLOCK_SUPPRESSION_STATE_CHANGED);
         context.registerReceiver(mReceiver, intentFilter, Context.RECEIVER_EXPORTED);
         mGraphHandlerThreads = new LinkedList<>();
+
+        mCallAnomalyWatchdog = callAnomalyWatchdog;
+        mAsyncTaskExecutor = asyncTaskExecutor;
     }
 
     public void setIncomingCallNotifier(IncomingCallNotifier incomingCallNotifier) {
@@ -638,9 +719,11 @@
     }
 
     @Override
+    @VisibleForTesting
     public void onSuccessfulOutgoingCall(Call call, int callState) {
         Log.v(this, "onSuccessfulOutgoingCall, %s", call);
-        call.setPostCallPackageName(getRoleManagerAdapter().getDefaultCallScreeningApp());
+        call.setPostCallPackageName(getRoleManagerAdapter().getDefaultCallScreeningApp(
+                call.getUserHandleFromTargetPhoneAccount()));
 
         setCallState(call, callState, "successful outgoing call");
         if (!mCalls.contains(call)) {
@@ -659,8 +742,7 @@
 
     @Override
     public void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause) {
-        Log.v(this, "onFailedOutgoingCall, call: %s", call);
-
+        Log.i(this, "onFailedOutgoingCall for call %s", call);
         markCallAsRemoved(call);
     }
 
@@ -673,12 +755,19 @@
             phoneAccount == null || phoneAccount.getExtras() == null
                 ? new Bundle()
                 : phoneAccount.getExtras();
+        TelephonyManager telephonyManager = getTelephonyManager();
         if (incomingCall.hasProperty(Connection.PROPERTY_EMERGENCY_CALLBACK_MODE) ||
+                incomingCall.hasProperty(Connection.PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL) ||
+                telephonyManager.isInEmergencySmsMode() ||
                 incomingCall.isSelfManaged() ||
                 extras.getBoolean(PhoneAccount.EXTRA_SKIP_CALL_FILTERING)) {
-            Log.i(this, "Skipping call filtering for %s (ecm=%b, selfMgd=%b, skipExtra=%b)",
+            Log.i(this, "Skipping call filtering for %s (ecm=%b, "
+                            + "networkIdentifiedEmergencyCall = %b, emergencySmsMode = %b, "
+                            + "selfMgd=%b, skipExtra=%b)",
                     incomingCall.getId(),
                     incomingCall.hasProperty(Connection.PROPERTY_EMERGENCY_CALLBACK_MODE),
+                    incomingCall.hasProperty(Connection.PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL),
+                    telephonyManager.isInEmergencySmsMode(),
                     incomingCall.isSelfManaged(),
                     extras.getBoolean(PhoneAccount.EXTRA_SKIP_CALL_FILTERING));
             onCallFilteringComplete(incomingCall, new Builder()
@@ -698,8 +787,11 @@
     private IncomingCallFilterGraph setUpCallFilterGraph(Call incomingCall) {
         incomingCall.setIsUsingCallFiltering(true);
         String carrierPackageName = getCarrierPackageName();
-        String defaultDialerPackageName = TelecomManager.from(mContext).getDefaultDialerPackage();
-        String userChosenPackageName = getRoleManagerAdapter().getDefaultCallScreeningApp();
+        UserHandle userHandle = incomingCall.getUserHandleFromTargetPhoneAccount();
+        String defaultDialerPackageName = TelecomManager.from(mContext).
+                getDefaultDialerPackage(userHandle);
+        String userChosenPackageName = getRoleManagerAdapter().
+                getDefaultCallScreeningApp(userHandle);
         AppLabelProxy appLabelProxy = packageName -> AppLabelProxy.Util.getAppLabel(
                 mContext.getPackageManager(), packageName);
         ParcelableCallUtils.Converter converter = new ParcelableCallUtils.Converter();
@@ -710,6 +802,7 @@
                 mCallerInfoLookupHelper);
         BlockCheckerFilter blockCheckerFilter = new BlockCheckerFilter(mContext, incomingCall,
                 mCallerInfoLookupHelper, new BlockCheckerAdapter());
+        DndCallFilter dndCallFilter = new DndCallFilter(incomingCall, getRinger());
         CallScreeningServiceFilter carrierCallScreeningServiceFilter =
                 new CallScreeningServiceFilter(incomingCall, carrierPackageName,
                         CallScreeningServiceFilter.PACKAGE_TYPE_CARRIER, mContext, this,
@@ -727,6 +820,7 @@
                     mContext, this, appLabelProxy, converter);
         }
         graph.addFilter(voicemailFilter);
+        graph.addFilter(dndCallFilter);
         graph.addFilter(blockCheckerFilter);
         graph.addFilter(carrierCallScreeningServiceFilter);
         graph.addFilter(callScreeningServiceFilter);
@@ -774,6 +868,9 @@
             return;
         }
 
+        // Store the shouldSuppress value in the call object which will be passed to InCallServices
+        incomingCall.setCallIsSuppressedByDoNotDisturb(result.shouldSuppressCallDueToDndStatus);
+
         // Inform our connection service that call filtering is done (if it was performed at all).
         if (incomingCall.isUsingCallFiltering()) {
             boolean isInContacts = incomingCall.getCallerInfo() != null
@@ -791,8 +888,7 @@
         }
 
         // Get rid of the call composer attachments that aren't wanted
-        if (result.mIsResponseFromSystemDialer && result.mCallScreeningResponse != null
-                && result.mCallScreeningResponse.getCallComposerAttachmentsToShow() >= 0) {
+        if (result.mIsResponseFromSystemDialer && result.mCallScreeningResponse != null) {
             int attachmentMask = result.mCallScreeningResponse.getCallComposerAttachmentsToShow();
             if ((attachmentMask
                     & CallScreeningService.CallResponse.CALL_COMPOSER_ATTACHMENT_LOCATION) == 0) {
@@ -812,7 +908,9 @@
 
         if (result.shouldAllowCall) {
             incomingCall.setPostCallPackageName(
-                    getRoleManagerAdapter().getDefaultCallScreeningApp());
+                    getRoleManagerAdapter().getDefaultCallScreeningApp(
+                            incomingCall.getUserHandleFromTargetPhoneAccount()
+                    ));
 
             Log.i(this, "onCallFilteringComplete: allow call.");
             if (hasMaximumManagedRingingCalls(incomingCall)) {
@@ -861,7 +959,8 @@
                 }
                 mCallLogManager.logCall(incomingCall, Calls.BLOCKED_TYPE,
                         result.shouldShowNotification, result);
-            } else if (result.shouldShowNotification) {
+            }
+            if (result.shouldShowNotification) {
                 Log.i(this, "onCallScreeningCompleted: blocked call, showing notification.");
                 mMissedCallNotifier.showMissedCallNotification(
                         new MissedCallNotifier.CallInfo(incomingCall));
@@ -901,14 +1000,15 @@
 
     @Override
     public void onFailedIncomingCall(Call call) {
+        Log.i(this, "onFailedIncomingCall for call %s", call);
         setCallState(call, CallState.DISCONNECTED, "failed incoming call");
         call.removeListener(this);
     }
 
     @Override
     public void onSuccessfulUnknownCall(Call call, int callState) {
-        setCallState(call, callState, "successful unknown call");
         Log.i(this, "onSuccessfulUnknownCall for call %s", call);
+        setCallState(call, callState, "successful unknown call");
         addCall(call);
     }
 
@@ -1128,7 +1228,6 @@
         }
     }
 
-    @VisibleForTesting
     public Call getForegroundCall() {
         if (mCallAudioManager == null) {
             // Happens when getForegroundCall is called before full initialization.
@@ -1137,6 +1236,15 @@
         return mCallAudioManager.getForegroundCall();
     }
 
+    @VisibleForTesting
+    public Set<Call> getTrackedCalls() {
+        if (mCallAudioManager == null) {
+            // Happens when getTrackedCalls is called before full initialization.
+            return null;
+        }
+        return mCallAudioManager.getTrackedCalls();
+    }
+
     @Override
     public void onCallHoldFailed(Call call) {
         markAllAnsweredCallAsRinging(call, "hold");
@@ -1172,10 +1280,18 @@
         return mInCallController;
     }
 
+    public CallEndpointController getCallEndpointController() {
+        return mCallEndpointController;
+    }
+
     EmergencyCallHelper getEmergencyCallHelper() {
         return mEmergencyCallHelper;
     }
 
+    EmergencyCallDiagnosticLogger getEmergencyCallDiagnosticLogger() {
+        return mEmergencyCallDiagnosticLogger;
+    }
+
     public DefaultDialerCache getDefaultDialerCache() {
         return mDefaultDialerCache;
     }
@@ -1239,6 +1355,11 @@
         mListeners.remove(listener);
     }
 
+    @VisibleForTesting
+    public void setAnomalyReporterAdapter(AnomalyReporterAdapter mAnomalyReporterAdapter){
+        mAnomalyReporter = mAnomalyReporterAdapter;
+    }
+
     void processIncomingConference(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
         Log.d(this, "processIncomingCallConference");
         processIncomingCallIntent(phoneAccountHandle, extras, true);
@@ -1255,7 +1376,7 @@
         processIncomingCallIntent(phoneAccountHandle, extras, false);
     }
 
-    void processIncomingCallIntent(PhoneAccountHandle phoneAccountHandle, Bundle extras,
+    public Call processIncomingCallIntent(PhoneAccountHandle phoneAccountHandle, Bundle extras,
         boolean isConference) {
         Log.d(this, "processIncomingCallIntent");
         boolean isHandover = extras.getBoolean(TelecomManager.EXTRA_IS_HANDOVER);
@@ -1265,7 +1386,7 @@
             handle = extras.getParcelable(TelephonyManager.EXTRA_INCOMING_NUMBER);
         }
         Call call = new Call(
-                getNextCallId(),
+                generateNextCallId(extras),
                 mContext,
                 this,
                 mLock,
@@ -1281,7 +1402,17 @@
                 mClockProxy,
                 mToastFactory);
 
-        // Ensure new calls related to self-managed calls/connections are set as such.  This will
+        // set properties for transactional call
+        if (extras.containsKey(TelecomManager.TRANSACTION_CALL_ID_KEY)) {
+            call.setIsTransactionalCall(true);
+            call.setCallingPackageIdentity(extras);
+            call.setConnectionCapabilities(
+                    extras.getInt(CallAttributes.CALL_CAPABILITIES_KEY,
+                            CallAttributes.SUPPORTS_SET_INACTIVE), true);
+            call.setTargetPhoneAccount(phoneAccountHandle);
+        }
+
+        // Ensure new calls related to self-managed calls/connections are set as such. This will
         // be overridden when the actual connection is returned in startCreateConnection, however
         // doing this now ensures the logs and any other logic will treat this call as self-managed
         // from the moment it is created.
@@ -1298,7 +1429,7 @@
                         PhoneAccount.EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE, true));
             } else {
                 // Incoming call is managed, the active call is self-managed and can't be held.
-                // We need to set extras on it to indicate whether answering will cause a 
+                // We need to set extras on it to indicate whether answering will cause a
                 // active self-managed call to drop.
                 Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
                 if (activeCall != null && !canHold(activeCall) && activeCall.isSelfManaged()) {
@@ -1310,7 +1441,7 @@
                     dropCallExtras.putCharSequence(
                             Connection.EXTRA_ANSWERING_DROPS_FG_CALL_APP_NAME, droppedApp);
                     Log.i(this, "Incoming managed call will drop %s call.", droppedApp);
-                    call.putExtras(Call.SOURCE_CONNECTION_SERVICE, dropCallExtras);
+                    call.putConnectionServiceExtras(dropCallExtras);
                 }
             }
 
@@ -1400,16 +1531,20 @@
             }
         }
 
-        if (!isHandoverAllowed || (call.isSelfManaged() && !isIncomingCallPermitted(call,
-                call.getTargetPhoneAccount()))) {
+        CallFailureCause startFailCause =
+                checkIncomingCallPermitted(call, call.getTargetPhoneAccount());
+        if (!isHandoverAllowed ||
+                (call.isSelfManaged() && !startFailCause.isSuccess())) {
             if (isConference) {
                 notifyCreateConferenceFailed(phoneAccountHandle, call);
             } else {
                 if (hasMaximumManagedRingingCalls(call)) {
                     call.setMissedReason(AUTO_MISSED_MAXIMUM_RINGING);
+                    call.setStartFailCause(CallFailureCause.MAX_RINGING_CALLS);
                     mCallLogManager.logCall(call, Calls.MISSED_TYPE,
                             true /*showNotificationForMissedCall*/, null /*CallFilteringResult*/);
                 }
+                call.setStartFailCause(startFailCause);
                 notifyCreateConnectionFailed(phoneAccountHandle, call);
             }
         } else if (isInEmergencyCall()) {
@@ -1418,6 +1553,7 @@
             // rejected since the user did not explicitly reject.
             call.setMissedReason(AUTO_MISSED_EMERGENCY_CALL);
             call.getAnalytics().setMissedReason(call.getMissedReason());
+            call.setStartFailCause(CallFailureCause.IN_EMERGENCY_CALL);
             mCallLogManager.logCall(call, Calls.MISSED_TYPE,
                     true /*showNotificationForMissedCall*/, null /*CallFilteringResult*/);
             if (isConference) {
@@ -1425,9 +1561,18 @@
             } else {
                 notifyCreateConnectionFailed(phoneAccountHandle, call);
             }
+        } else if (call.isTransactionalCall()) {
+            // transactional calls should skip Call#startCreateConnection below
+            // as that is meant for Call objects with a ConnectionServiceWrapper
+            call.setState(CallState.RINGING, "explicitly set new incoming to ringing");
+            // Transactional calls don't get created via a connection service; they are added now.
+            call.setIsCreateConnectionComplete(true);
+            addCall(call);
         } else {
+            notifyStartCreateConnection(call);
             call.startCreateConnection(mPhoneAccountRegistrar);
         }
+        return call;
     }
 
     void addNewUnknownCall(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
@@ -1455,6 +1600,7 @@
 
         setIntentExtrasAndStartTime(call, extras);
         call.addListener(this);
+        notifyStartCreateConnection(call);
         call.startCreateConnection(mPhoneAccountRegistrar);
     }
 
@@ -1518,6 +1664,14 @@
                 originalIntent, callingPackage, false);
     }
 
+    private String generateNextCallId(Bundle extras) {
+        if (extras != null && extras.containsKey(TelecomManager.TRANSACTION_CALL_ID_KEY)) {
+            return extras.getString(TelecomManager.TRANSACTION_CALL_ID_KEY);
+        } else {
+            return getNextCallId();
+        }
+    }
+
     private CompletableFuture<Call> startOutgoingCall(List<Uri> participants,
             PhoneAccountHandle requestedAccountHandle,
             Bundle extras, UserHandle initiatingUser, Intent originalIntent,
@@ -1544,7 +1698,7 @@
         // Create a call with original handle. The handle may be changed when the call is attached
         // to a connection service, but in most cases will remain the same.
         if (call == null) {
-            call = new Call(getNextCallId(), mContext,
+            call = new Call(generateNextCallId(extras), mContext,
                     this,
                     mLock,
                     mConnectionServiceRepository,
@@ -1559,8 +1713,35 @@
                     isConference, /* isConference */
                     mClockProxy,
                     mToastFactory);
+
+            if (extras.containsKey(TelecomManager.TRANSACTION_CALL_ID_KEY)) {
+                call.setIsTransactionalCall(true);
+                call.setCallingPackageIdentity(extras);
+                call.setConnectionCapabilities(
+                        extras.getInt(CallAttributes.CALL_CAPABILITIES_KEY,
+                                CallAttributes.SUPPORTS_SET_INACTIVE), true);
+                call.setTargetPhoneAccount(requestedAccountHandle);
+            }
+
             call.initAnalytics(callingPackage, creationLogs.toString());
 
+            // Log info for emergency call
+            if (call.isEmergencyCall()) {
+                String simNumeric = "";
+                String networkNumeric = "";
+                int defaultVoiceSubId = SubscriptionManager.getDefaultVoiceSubscriptionId();
+                if (defaultVoiceSubId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+                    TelephonyManager tm = getTelephonyManager().createForSubscriptionId(
+                            defaultVoiceSubId);
+                    CellIdentity cellIdentity = tm.getLastKnownCellIdentity();
+                    simNumeric = tm.getSimOperatorNumeric();
+                    networkNumeric = (cellIdentity != null) ? cellIdentity.getPlmn() : "";
+                }
+                TelecomStatsLog.write(TelecomStatsLog.EMERGENCY_NUMBER_DIALED,
+                            handle.getSchemeSpecificPart(),
+                            callingPackage, simNumeric, networkNumeric);
+            }
+
             // Ensure new calls related to self-managed calls/connections are set as such.  This
             // will be overridden when the actual connection is returned in startCreateConnection,
             // however doing this now ensures the logs and any other logic will treat this call as
@@ -1631,6 +1812,18 @@
         // retrieved.
         CompletableFuture<List<PhoneAccountHandle>> setAccountHandle =
                 accountsForCall.whenCompleteAsync((potentialPhoneAccounts, exception) -> {
+                    if (exception != null){
+                        Log.e(TAG, exception, "Error retrieving list of potential phone accounts.");
+                        if (finalCall.isEmergencyCall()) {
+                            mAnomalyReporter.reportAnomaly(
+                                    EXCEPTION_RETRIEVING_PHONE_ACCOUNTS_EMERGENCY_ERROR_UUID,
+                                    EXCEPTION_RETRIEVING_PHONE_ACCOUNTS_EMERGENCY_ERROR_MSG);
+                        } else {
+                            mAnomalyReporter.reportAnomaly(
+                                    EXCEPTION_RETRIEVING_PHONE_ACCOUNTS_ERROR_UUID,
+                                    EXCEPTION_RETRIEVING_PHONE_ACCOUNTS_ERROR_MSG);
+                        }
+                    }
                     Log.i(CallsManager.this, "set outgoing call phone acct; potentialAccts=%s",
                             potentialPhoneAccounts);
                     PhoneAccountHandle phoneAccountHandle;
@@ -1703,6 +1896,7 @@
                             notifyCreateConnectionFailed(
                                     finalCall.getTargetPhoneAccount(), finalCall);
                         }
+                        finalCall.setStartFailCause(CallFailureCause.IN_EMERGENCY_CALL);
                         return CompletableFuture.completedFuture(null);
                     }
 
@@ -1761,9 +1955,36 @@
                                 return CompletableFuture.completedFuture(null);
                             }
                             if (accountSuggestions == null || accountSuggestions.isEmpty()) {
+                                if (isSwitchToManagedProfileDialogFlagEnabled()) {
+                                    Uri callUri = callToPlace.getHandle();
+                                    if (PhoneAccount.SCHEME_TEL.equals(callUri.getScheme())) {
+                                        int managedProfileUserId = getManagedProfileUserId(mContext,
+                                                initiatingUser.getIdentifier());
+                                        if (managedProfileUserId != UserHandle.USER_NULL
+                                                &&
+                                                mPhoneAccountRegistrar.getCallCapablePhoneAccounts(
+                                                        handle.getScheme(), false,
+                                                        UserHandle.of(managedProfileUserId),
+                                                        false).size()
+                                                        != 0) {
+                                            boolean dialogShown = showSwitchToManagedProfileDialog(
+                                                    callUri, initiatingUser, managedProfileUserId);
+                                            if (dialogShown) {
+                                                return CompletableFuture.completedFuture(null);
+                                            }
+                                        }
+                                    }
+                                }
+
                                 Log.i(CallsManager.this, "Aborting call since there are no"
                                         + " available accounts.");
                                 showErrorMessage(R.string.cant_call_due_to_no_supported_service);
+                                mListeners.forEach(l -> l.onCreateConnectionFailed(callToPlace));
+                                if (callToPlace.isEmergencyCall()){
+                                    mAnomalyReporter.reportAnomaly(
+                                            EMERGENCY_CALL_ABORTED_NO_PHONE_ACCOUNTS_ERROR_UUID,
+                                            EMERGENCY_CALL_ABORTED_NO_PHONE_ACCOUNTS_ERROR_MSG);
+                                }
                                 return CompletableFuture.completedFuture(null);
                             }
                             boolean needsAccountSelection = accountSuggestions.size() > 1
@@ -1810,6 +2031,8 @@
             dialerSelectPhoneAccountFuture.thenAcceptBothAsync(contactLookupFuture,
                     (callPhoneAccountHandlePair, uriCallerInfoPair) -> {
                         Call theCall = callPhoneAccountHandlePair.first;
+                        UserHandle userHandleForCallScreening = theCall.
+                                getUserHandleFromTargetPhoneAccount();
                         boolean isInContacts = uriCallerInfoPair.second != null
                                 && uriCallerInfoPair.second.contactExists;
                         Log.d(CallsManager.this, "outgoingCallIdStage: isInContacts=%s",
@@ -1820,10 +2043,12 @@
                         PackageManager packageManager = mContext.getPackageManager();
                         int permission = packageManager.checkPermission(
                                 Manifest.permission.READ_CONTACTS,
-                                mRoleManagerAdapter.getDefaultCallScreeningApp());
+                                mRoleManagerAdapter.
+                                        getDefaultCallScreeningApp(userHandleForCallScreening));
                         Log.d(CallsManager.this,
                                 "default call screening service package %s has permissions=%s",
-                                mRoleManagerAdapter.getDefaultCallScreeningApp(),
+                                mRoleManagerAdapter.
+                                        getDefaultCallScreeningApp(userHandleForCallScreening),
                                 permission == PackageManager.PERMISSION_GRANTED);
                         if ((!isInContacts) || (permission == PackageManager.PERMISSION_GRANTED)) {
                             bindForOutgoingCallerId(theCall);
@@ -1895,6 +2120,46 @@
         return mLatestPostSelectionProcessingFuture;
     }
 
+    private static int getManagedProfileUserId(Context context, int userId) {
+        UserManager um = context.getSystemService(UserManager.class);
+        List<UserInfo> userProfiles = um.getProfiles(userId);
+        for (UserInfo uInfo : userProfiles) {
+            if (uInfo.id == userId) {
+                continue;
+            }
+            if (uInfo.isManagedProfile()) {
+                return uInfo.id;
+            }
+        }
+        return UserHandle.USER_NULL;
+    }
+
+    private boolean isSwitchToManagedProfileDialogFlagEnabled() {
+        return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER,
+                "enable_switch_to_managed_profile_dialog", false);
+    }
+
+    private boolean showSwitchToManagedProfileDialog(Uri callUri, UserHandle initiatingUser,
+            int managedProfileUserId) {
+        try {
+            Intent showErrorIntent = new Intent(
+                    TelecomManager.ACTION_SHOW_SWITCH_TO_WORK_PROFILE_FOR_CALL_DIALOG, callUri);
+            showErrorIntent.addCategory(Intent.CATEGORY_DEFAULT);
+            showErrorIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            showErrorIntent.putExtra(TelecomManager.EXTRA_MANAGED_PROFILE_USER_ID,
+                    managedProfileUserId);
+
+            if (mContext.getPackageManager().queryIntentActivitiesAsUser(showErrorIntent,
+                    ResolveInfoFlags.of(0), initiatingUser).size() != 0) {
+                mContext.startActivityAsUser(showErrorIntent, initiatingUser);
+                return true;
+            }
+        } catch (Exception e) {
+            Log.w(this, "Failed to launch switch to managed profile dialog");
+        }
+        return false;
+    }
+
     public void startConference(List<Uri> participants, Bundle clientExtras, String callingPackage,
             UserHandle initiatingUser) {
 
@@ -1934,7 +2199,8 @@
     private void bindForOutgoingCallerId(Call theCall) {
         // Find the user chosen call screening app.
         String callScreeningApp =
-                mRoleManagerAdapter.getDefaultCallScreeningApp();
+                mRoleManagerAdapter.getDefaultCallScreeningApp(
+                        theCall.getUserHandleFromTargetPhoneAccount());
 
         CompletableFuture future =
                 new CallScreeningServiceHelper(mContext,
@@ -2090,27 +2356,31 @@
 
         boolean endEarly = false;
         String disconnectReason = "";
-        String callRedirectionApp = mRoleManagerAdapter.getDefaultCallRedirectionApp();
+        String callRedirectionApp = mRoleManagerAdapter.getDefaultCallRedirectionApp(
+                phoneAccountHandle.getUserHandle());
         PhoneAccount phoneAccount = mPhoneAccountRegistrar
                 .getPhoneAccountUnchecked(phoneAccountHandle);
         if (phoneAccount != null
                 && !phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
+            // Note that mCurrentUserHandle may not actually be the current user, i.e.
+            // in the case of work profiles
+            UserHandle currentUserHandle = call.getUserHandleFromTargetPhoneAccount();
             // Check if the phoneAccountHandle belongs to the current user
             if (phoneAccountHandle != null &&
-                    !phoneAccountHandle.getUserHandle().equals(call.getInitiatingUser())) {
+                    !phoneAccountHandle.getUserHandle().equals(currentUserHandle)) {
                 phoneAccountHandle = null;
             }
         }
 
-        boolean isPotentialEmergencyNumber;
+        boolean isEmergencyNumber;
         try {
-            isPotentialEmergencyNumber =
-                    handle != null && getTelephonyManager().isPotentialEmergencyNumber(
+            isEmergencyNumber =
+                    handle != null && getTelephonyManager().isEmergencyNumber(
                             handle.getSchemeSpecificPart());
         } catch (IllegalStateException ise) {
-            isPotentialEmergencyNumber = false;
+            isEmergencyNumber = false;
         } catch (RuntimeException r) {
-            isPotentialEmergencyNumber = false;
+            isEmergencyNumber = false;
         }
 
         if (shouldCancelCall) {
@@ -2138,7 +2408,7 @@
             Log.w(this, "onCallRedirectionComplete: phoneAccountHandle is unavailable");
             endEarly = true;
             disconnectReason = "Unavailable phoneAccountHandle from Call Redirection Service";
-        } else if (isPotentialEmergencyNumber) {
+        } else if (isEmergencyNumber) {
             Log.w(this, "onCallRedirectionComplete: emergency number %s is redirected from Call"
                     + " Redirection Service", handle.getSchemeSpecificPart());
             endEarly = true;
@@ -2420,12 +2690,26 @@
                     // Drop any ongoing self-managed calls to make way for an emergency call.
                     disconnectSelfManagedCalls("place emerg call" /* reason */);
                 }
+                try {
+                    notifyStartCreateConnection(call);
+                    call.startCreateConnection(mPhoneAccountRegistrar);
+                } catch (Exception exception) {
+                    // If an exceptions is thrown while creating the connection, prompt the user to
+                    // generate a bugreport and force disconnect.
+                    Log.e(TAG, exception, "Exception thrown while establishing connection.");
+                    mAnomalyReporter.reportAnomaly(
+                            EXCEPTION_WHILE_ESTABLISHING_CONNECTION_ERROR_UUID,
+                            EXCEPTION_WHILE_ESTABLISHING_CONNECTION_ERROR_MSG);
+                    markCallAsDisconnected(call,
+                            new DisconnectCause(DisconnectCause.ERROR,
+                            "Failed to create the connection."));
+                    markCallAsRemoved(call);
+                }
 
-                call.startCreateConnection(mPhoneAccountRegistrar);
             }
         } else if (mPhoneAccountRegistrar.getCallCapablePhoneAccounts(
                 requireCallCapableAccountByHandle ? callHandleScheme : null, false,
-                call.getInitiatingUser()).isEmpty()) {
+                call.getInitiatingUser(), false).isEmpty()) {
             // If there are no call capable accounts, disconnect the call.
             markCallAsDisconnected(call, new DisconnectCause(DisconnectCause.CANCELED,
                     "No registered PhoneAccounts"));
@@ -2456,6 +2740,10 @@
     public void answerCall(Call call, int videoState) {
         if (!mCalls.contains(call)) {
             Log.i(this, "Request to answer a non-existent call %s", call);
+        } else if (call.isTransactionalCall()) {
+            // InCallAdapter is requesting to answer the given transactioanl call. Must get an ack
+            // from the client via a transaction before answering.
+            call.answer(videoState);
         } else {
             // Hold or disconnect the active call and request call focus for the incoming call.
             Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
@@ -2849,7 +3137,7 @@
     }
 
     @Override
-    public void onExtrasChanged(Call c, int source, Bundle extras) {
+    public void onExtrasChanged(Call c, int source, Bundle extras, String requestingPackageName) {
         if (source != Call.SOURCE_CONNECTION_SERVICE) {
             return;
         }
@@ -2877,6 +3165,18 @@
         return constructPossiblePhoneAccounts(handle, user, isVideo, isEmergency, false);
     }
 
+    // Returns whether the device is capable of 2 simultaneous active voice calls on different subs.
+    private boolean isDsdaCallingPossible() {
+        try {
+            return getTelephonyManager().getMaxNumberOfSimultaneouslyActiveSims() > 1
+                    || getTelephonyManager().getPhoneCapability()
+                           .getMaxActiveVoiceSubscriptions() > 1;
+        } catch (Exception e) {
+            Log.w(this, "exception in isDsdaCallingPossible(): ", e);
+            return false;
+        }
+    }
+
     public List<PhoneAccountHandle> constructPossiblePhoneAccounts(Uri handle, UserHandle user,
             boolean isVideo, boolean isEmergency, boolean isConference) {
 
@@ -2891,14 +3191,14 @@
         List<PhoneAccountHandle> allAccounts =
                 mPhoneAccountRegistrar.getCallCapablePhoneAccounts(handle.getScheme(), false, user,
                         capabilities,
-                        isEmergency ? 0 : PhoneAccount.CAPABILITY_EMERGENCY_CALLS_ONLY);
-        if (mMaxNumberOfSimultaneouslyActiveSims < 0) {
-            mMaxNumberOfSimultaneouslyActiveSims =
-                    getTelephonyManager().getMaxNumberOfSimultaneouslyActiveSims();
-        }
+                        isEmergency ? 0 : PhoneAccount.CAPABILITY_EMERGENCY_CALLS_ONLY,
+                        isEmergency);
         // Only one SIM PhoneAccount can be active at one time for DSDS. Only that SIM PhoneAccount
         // should be available if a call is already active on the SIM account.
-        if (mMaxNumberOfSimultaneouslyActiveSims == 1) {
+        // Similarly, the emergency call should be attempted over the same PhoneAccount as the
+        // ongoing call. However, if the ongoing call is over cross-SIM registration, then the
+        // emergency call will be attempted over a different Phone object at a later stage.
+        if (isEmergency || !isDsdaCallingPossible()) {
             List<PhoneAccountHandle> simAccounts =
                     mPhoneAccountRegistrar.getSimPhoneAccountsOfCurrentUser();
             PhoneAccountHandle ongoingCallAccount = null;
@@ -2937,6 +3237,14 @@
         }
     }
 
+    @Override
+    public void onCallStreamingStateChanged(Call call, boolean isStreaming) {
+        Log.v(this, "onCallStreamingStateChanged: %b", isStreaming);
+        for (CallsManagerListener listener : mListeners) {
+            listener.onCallStreamingStateChanged(call, isStreaming);
+        }
+    }
+
     private void handleCallTechnologyChange(Call call) {
         if (call.getExtras() != null
                 && call.getExtras().containsKey(TelecomManager.EXTRA_CALL_TECHNOLOGY_TYPE)) {
@@ -2976,6 +3284,14 @@
         mCallAudioManager.setAudioRoute(route, bluetoothAddress);
     }
 
+    /**
+      * Called by the in-call UI to change the CallEndpoint
+      */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void requestCallEndpointChange(CallEndpoint endpoint, ResultReceiver callback) {
+        mCallEndpointController.requestCallEndpointChange(endpoint, callback);
+    }
+
     /** Called by the in-call UI to turn the proximity sensor on. */
     void turnOnProximitySensor() {
         mProximitySensorManager.turnOn();
@@ -3034,6 +3350,30 @@
         }
     }
 
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void updateCallEndpoint(CallEndpoint callEndpoint) {
+        Log.v(this, "updateCallEndpoint");
+        for (CallsManagerListener listener : mListeners) {
+            listener.onCallEndpointChanged(callEndpoint);
+        }
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void updateAvailableCallEndpoints(Set<CallEndpoint> availableCallEndpoints) {
+        Log.v(this, "updateAvailableCallEndpoints");
+        for (CallsManagerListener listener : mListeners) {
+            listener.onAvailableCallEndpointsChanged(availableCallEndpoints);
+        }
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void updateMuteState(boolean isMuted) {
+        Log.v(this, "updateMuteState");
+        for (CallsManagerListener listener : mListeners) {
+            listener.onMuteStateChanged(isMuted);
+        }
+    }
+
     /**
      * Called when disconnect tone is started or stopped, including any InCallTone
      * after disconnected call.
@@ -3053,7 +3393,8 @@
         setCallState(call, CallState.RINGING, "ringing set explicitly");
     }
 
-    void markCallAsDialing(Call call) {
+    @VisibleForTesting
+    public void markCallAsDialing(Call call) {
         setCallState(call, CallState.DIALING, "dialing set explicitly");
         maybeMoveToSpeakerPhone(call);
         maybeTurnOffMute(call);
@@ -3099,6 +3440,7 @@
                 Log.i(this, "holdActiveCallForNewCall: Holding active %s before making %s active.",
                         activeCall.getId(), call.getId());
                 activeCall.hold();
+                call.increaseHeldByThisCallCount();
                 return true;
             } else {
                 // This call does not support hold. If it is from a different connection
@@ -3124,6 +3466,45 @@
         return false;
     }
 
+    // attempt to hold the requested call and complete the callback on the result
+    public void transactionHoldPotentialActiveCallForNewCall(Call newCall,
+            OutcomeReceiver<Boolean, CallException> callback) {
+        Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
+        Log.i(this, "transactionHoldPotentialActiveCallForNewCall: "
+                + "newCall=[%s], activeCall=[%s]", newCall, activeCall);
+
+        // early exit if there is no need to hold an active call
+        if (activeCall == null || activeCall == newCall) {
+            Log.i(this, "transactionHoldPotentialActiveCallForNewCall:"
+                    + " no need to hold activeCall");
+            callback.onResult(true);
+            return;
+        }
+
+        // before attempting CallsManager#holdActiveCallForNewCall(Call), check if it'll fail early
+        if (!canHold(activeCall) &&
+                !(supportsHold(activeCall) && areFromSameSource(activeCall, newCall))) {
+            Log.i(this, "transactionHoldPotentialActiveCallForNewCall: "
+                    + "conditions show the call cannot be held.");
+            callback.onError(new CallException("call does not support hold",
+                    CallException.CODE_CANNOT_HOLD_CURRENT_ACTIVE_CALL));
+            return;
+        }
+
+        // attempt to hold the active call
+        if (!holdActiveCallForNewCall(newCall)) {
+            Log.i(this, "transactionHoldPotentialActiveCallForNewCall: "
+                    + "attempted to hold call but failed.");
+            callback.onError(new CallException("cannot hold active call failed",
+                    CallException.CODE_CANNOT_HOLD_CURRENT_ACTIVE_CALL));
+            return;
+        }
+
+        // officially mark the activeCall as held
+        markCallAsOnHold(activeCall);
+        callback.onResult(true);
+    }
+
     @VisibleForTesting
     public void markCallAsActive(Call call) {
         Log.i(this, "markCallAsActive, isSelfManaged: " + call.isSelfManaged());
@@ -3158,7 +3539,6 @@
         }
     }
 
-    @VisibleForTesting
     public void markCallAsOnHold(Call call) {
         setCallState(call, CallState.ON_HOLD, "on-hold set explicitly");
     }
@@ -3169,8 +3549,9 @@
      *
      * @param disconnectCause The disconnect cause, see {@link android.telecom.DisconnectCause}.
      */
-    @VisibleForTesting
     public void markCallAsDisconnected(Call call, DisconnectCause disconnectCause) {
+        Log.i(this, "markCallAsDisconnected: call=%s; disconnectCause=%s",
+                call.toString(), disconnectCause.toString());
         int oldState = call.getState();
         if (call.getState() == CallState.SIMULATED_RINGING
                 && disconnectCause.getCode() == DisconnectCause.REMOTE) {
@@ -3195,6 +3576,17 @@
             }
         }
 
+        // Notify listeners that the call was disconnected before being added to CallsManager.
+        // Listeners will not receive onAdded or onRemoved callbacks.
+        if (!mCalls.contains(call)) {
+            if (call.isEmergencyCall()) {
+                mAnomalyReporter.reportAnomaly(
+                        EMERGENCY_CALL_DISCONNECTED_BEFORE_BEING_ADDED_ERROR_UUID,
+                        EMERGENCY_CALL_DISCONNECTED_BEFORE_BEING_ADDED_ERROR_MSG);
+            }
+            mListeners.forEach(l -> l.onCreateConnectionFailed(call));
+        }
+
         // If a call diagnostic service is in use, we will log the original telephony-provided
         // disconnect cause, inform the CDS of the disconnection, and then chain the update of the
         // call state until AFTER the CDS reports it's result back.
@@ -3239,7 +3631,7 @@
     /**
      * Removes an existing disconnected call, and notifies the in-call app.
      */
-    void markCallAsRemoved(Call call) {
+    public void markCallAsRemoved(Call call) {
         if (call.isDisconnectHandledViaFuture()) {
             Log.i(this, "markCallAsRemoved; callid=%s, postingToFuture.", call.getId());
             // A future is being used due to a CallDiagnosticService handling the call.  We will
@@ -3264,38 +3656,57 @@
      * @param call The call.
      */
     private void performRemoval(Call call) {
-        mInCallController.getBindingFuture().thenRunAsync(() -> {
-            call.maybeCleanupHandover();
-            removeCall(call);
-            Call foregroundCall = mCallAudioManager.getPossiblyHeldForegroundCall();
-            if (mLocallyDisconnectingCalls.contains(call)) {
-                boolean isDisconnectingChildCall = call.isDisconnectingChildCall();
-                Log.v(this, "performRemoval: isDisconnectingChildCall = "
-                        + isDisconnectingChildCall + "call -> %s", call);
-                mLocallyDisconnectingCalls.remove(call);
-                // Auto-unhold the foreground call due to a locally disconnected call, except if the
-                // call which was disconnected is a member of a conference (don't want to auto
-                // un-hold the conference if we remove a member of the conference).
-                if (!isDisconnectingChildCall && foregroundCall != null
-                        && foregroundCall.getState() == CallState.ON_HOLD) {
-                    foregroundCall.unhold();
-                }
-            } else if (foregroundCall != null &&
-                    !foregroundCall.can(Connection.CAPABILITY_SUPPORT_HOLD) &&
-                    foregroundCall.getState() == CallState.ON_HOLD) {
+        if (mInCallController.getBindingFuture() != null) {
+            mInCallController.getBindingFuture().thenRunAsync(() -> {
+                        doRemoval(call);
+                    }, new LoggedHandlerExecutor(mHandler, "CM.pR", mLock))
+                    .exceptionally((throwable) -> {
+                        Log.e(TAG, throwable, "Error while executing call removal");
+                        mAnomalyReporter.reportAnomaly(CALL_REMOVAL_EXECUTION_ERROR_UUID,
+                                CALL_REMOVAL_EXECUTION_ERROR_MSG);
+                        return null;
+                    });
+        } else {
+            doRemoval(call);
+        }
+    }
 
-                // The new foreground call is on hold, however the carrier does not display the hold
-                // button in the UI.  Therefore, we need to auto unhold the held call since the user
-                // has no means of unholding it themselves.
-                Log.i(this, "performRemoval: Auto-unholding held foreground call (call doesn't "
-                        + "support hold)");
+    /**
+     * Code to perform removal of a call.  Called above from {@link #performRemoval(Call)} either
+     * async (in live code) or sync (in testing).
+     * @param call the call to remove.
+     */
+    private void doRemoval(Call call) {
+        call.maybeCleanupHandover();
+        removeCall(call);
+        Call foregroundCall = mCallAudioManager.getPossiblyHeldForegroundCall();
+        if (mLocallyDisconnectingCalls.contains(call)) {
+            boolean isDisconnectingChildCall = call.isDisconnectingChildCall();
+            Log.v(this, "performRemoval: isDisconnectingChildCall = "
+                    + isDisconnectingChildCall + "call -> %s", call);
+            mLocallyDisconnectingCalls.remove(call);
+            // Auto-unhold the foreground call due to a locally disconnected call, except if the
+            // call which was disconnected is a member of a conference (don't want to auto
+            // un-hold the conference if we remove a member of the conference).
+            // Also, ensure that the call we're removing is from the same ConnectionService as
+            // the one we're removing.  We don't want to auto-unhold between ConnectionService
+            // implementations, especially if one is managed and the other is a VoIP CS.
+            if (!isDisconnectingChildCall && foregroundCall != null
+                    && foregroundCall.getState() == CallState.ON_HOLD
+                    && areFromSameSource(foregroundCall, call)) {
                 foregroundCall.unhold();
             }
-        }, new LoggedHandlerExecutor(mHandler, "CM.pR", mLock))
-                .exceptionally((throwable) -> {
-                    Log.e(TAG, throwable, "Error while executing call removal");
-                    return null;
-                });
+        } else if (foregroundCall != null &&
+                !foregroundCall.can(Connection.CAPABILITY_SUPPORT_HOLD) &&
+                foregroundCall.getState() == CallState.ON_HOLD) {
+
+            // The new foreground call is on hold, however the carrier does not display the hold
+            // button in the UI.  Therefore, we need to auto unhold the held call since the user
+            // has no means of unholding it themselves.
+            Log.i(this, "performRemoval: Auto-unholding held foreground call (call doesn't "
+                    + "support hold)");
+            foregroundCall.unhold();
+        }
     }
 
     /**
@@ -3365,10 +3776,6 @@
         return false;
     }
 
-    boolean hasActiveOrHoldingCall() {
-        return getFirstCallWithState(CallState.ACTIVE, CallState.ON_HOLD) != null;
-    }
-
     boolean hasRingingCall() {
         return getFirstCallWithState(CallState.RINGING, CallState.ANSWERED) != null;
     }
@@ -3490,15 +3897,6 @@
         return getFirstCallWithState(CallState.ACTIVE);
     }
 
-    Call getDialingCall() {
-        return getFirstCallWithState(CallState.DIALING);
-    }
-
-    @VisibleForTesting
-    public Call getHeldCall() {
-        return getFirstCallWithState(CallState.ON_HOLD);
-    }
-
     public Call getHeldCallByConnectionService(PhoneAccountHandle targetPhoneAccount) {
         Optional<Call> heldCall = mCalls.stream()
                 .filter(call -> PhoneAccountHandle.areFromSamePackage(call.getTargetPhoneAccount(),
@@ -3622,6 +4020,11 @@
                 mClockProxy,
                 mToastFactory);
 
+        // Unlike connections, conferences are not created first and then notified as create
+        // connection complete from the CS.  They originate from the CS and are reported directly to
+        // telecom where they're added (see below).
+        call.setIsCreateConnectionComplete(true);
+
         setCallState(call, Call.getStateFromConnectionState(parcelableConference.getState()),
                 "new conference call");
         call.setHandle(parcelableConference.getHandle(),
@@ -3631,7 +4034,7 @@
         call.setVideoState(parcelableConference.getVideoState());
         call.setVideoProvider(parcelableConference.getVideoProvider());
         call.setStatusHints(parcelableConference.getStatusHints());
-        call.putExtras(Call.SOURCE_CONNECTION_SERVICE, parcelableConference.getExtras());
+        call.putConnectionServiceExtras(parcelableConference.getExtras());
         // In case this Conference was added via a ConnectionManager, keep track of the original
         // Connection ID as created by the originating ConnectionService.
         Bundle extras = parcelableConference.getExtras();
@@ -3716,6 +4119,10 @@
      */
     @VisibleForTesting
     public void addCall(Call call) {
+        if (mCalls.contains(call)) {
+            Log.i(this, "addCall(%s) is already added");
+            return;
+        }
         Trace.beginSection("addCall");
         Log.i(this, "addCall(%s)", call);
         call.addListener(this);
@@ -3748,6 +4155,11 @@
         Trace.beginSection("removeCall");
         Log.v(this, "removeCall(%s)", call);
 
+        if (call.isTransactionalCall() && call.getTransactionServiceWrapper() != null) {
+            // remove call from wrappers
+            call.getTransactionServiceWrapper().removeCallFromWrappers(call);
+        }
+
         call.setParentAndChildCall(null);  // clean up parent relationship before destroying.
         call.removeListener(this);
         call.clearConnectionService();
@@ -4101,6 +4513,60 @@
         return (int) callsStream.count();
     }
 
+    /**
+     * Determines the number of calls (visible to the calling user) matching the specified criteria.
+     * This is an overloaded method which is being used in a security patch to fix up the call
+     * state type APIs which are acting across users when they should not be.
+     *
+     * See {@link TelecomManager#isInCall()} and {@link TelecomManager#isInManagedCall()}.
+     *
+     * @param callFilter indicates whether to include just managed calls
+     *                   ({@link #CALL_FILTER_MANAGED}), self-managed calls
+     *                   ({@link #CALL_FILTER_SELF_MANAGED}), or all calls
+     *                   ({@link #CALL_FILTER_ALL}).
+     * @param excludeCall Where {@code non-null}, this call is excluded from the count.
+     * @param callingUser Where {@code non-null}, call visibility is scoped to this
+     *                    {@link UserHandle}.
+     * @param hasCrossUserAccess indicates if calling user has the INTERACT_ACROSS_USERS permission.
+     * @param phoneAccountHandle Where {@code non-null}, calls for this {@link PhoneAccountHandle}
+     *                           are excluded from the count.
+     * @param states The list of {@link CallState}s to include in the count.
+     * @return Count of calls matching criteria.
+     */
+    @VisibleForTesting
+    public int getNumCallsWithState(final int callFilter, Call excludeCall,
+            UserHandle callingUser, boolean hasCrossUserAccess,
+            PhoneAccountHandle phoneAccountHandle, int... states) {
+
+        Set<Integer> desiredStates = IntStream.of(states).boxed().collect(Collectors.toSet());
+
+        Stream<Call> callsStream = mCalls.stream()
+                .filter(call -> desiredStates.contains(call.getState()) &&
+                        call.getParentCall() == null && !call.isExternalCall());
+
+        if (callFilter == CALL_FILTER_MANAGED) {
+            callsStream = callsStream.filter(call -> !call.isSelfManaged());
+        } else if (callFilter == CALL_FILTER_SELF_MANAGED) {
+            callsStream = callsStream.filter(call -> call.isSelfManaged());
+        }
+
+        // If a call to exclude was specified, filter it out.
+        if (excludeCall != null) {
+            callsStream = callsStream.filter(call -> call != excludeCall);
+        }
+
+        // If a phone account handle was specified, only consider calls for that phone account.
+        if (phoneAccountHandle != null) {
+            callsStream = callsStream.filter(
+                    call -> phoneAccountHandle.equals(call.getTargetPhoneAccount()));
+        }
+
+        callsStream = callsStream.filter(
+                call -> hasCrossUserAccess || isCallVisibleForUser(call, callingUser));
+
+        return (int) callsStream.count();
+    }
+
     private boolean hasMaximumLiveCalls(Call exceptCall) {
         return MAXIMUM_LIVE_CALLS <= getNumCallsWithState(CALL_FILTER_ALL,
                 exceptCall, null /* phoneAccountHandle*/, LIVE_CALL_STATES);
@@ -4195,23 +4661,29 @@
     /**
      * Determines if there are any ongoing managed or self-managed calls.
      * Note: The {@link #ONGOING_CALL_STATES} are
+     * @param callingUser The user to scope the calls to.
+     * @param hasCrossUserAccess indicates if user has the INTERACT_ACROSS_USERS permission.
      * @return {@code true} if there are ongoing managed or self-managed calls, {@code false}
      *      otherwise.
      */
-    public boolean hasOngoingCalls() {
+    public boolean hasOngoingCalls(UserHandle callingUser, boolean hasCrossUserAccess) {
         return getNumCallsWithState(
                 CALL_FILTER_ALL, null /* excludeCall */,
+                callingUser, hasCrossUserAccess,
                 null /* phoneAccountHandle */,
                 ONGOING_CALL_STATES) > 0;
     }
 
     /**
      * Determines if there are any ongoing managed calls.
+     * @param callingUser The user to scope the calls to.
+     * @param hasCrossUserAccess indicates if user has the INTERACT_ACROSS_USERS permission.
      * @return {@code true} if there are ongoing managed calls, {@code false} otherwise.
      */
-    public boolean hasOngoingManagedCalls() {
+    public boolean hasOngoingManagedCalls(UserHandle callingUser, boolean hasCrossUserAccess) {
         return getNumCallsWithState(
                 CALL_FILTER_MANAGED, null /* excludeCall */,
+                callingUser, hasCrossUserAccess,
                 null /* phoneAccountHandle */,
                 ONGOING_CALL_STATES) > 0;
     }
@@ -4292,6 +4764,7 @@
             }
             //  If the user tries to make two outgoing calls to different emergency call numbers,
             //  we will try to connect the first outgoing call and reject the second.
+            emergencyCall.setStartFailCause(CallFailureCause.IN_EMERGENCY_CALL);
             return false;
         }
 
@@ -4302,6 +4775,12 @@
             return true;
         }
 
+        // If the live call is stuck in a connecting state, prompt the user to generate a bugreport.
+        if (liveCall.getState() == CallState.CONNECTING) {
+            mAnomalyReporter.reportAnomaly(LIVE_CALL_STUCK_CONNECTING_EMERGENCY_ERROR_UUID,
+                    LIVE_CALL_STUCK_CONNECTING_EMERGENCY_ERROR_MSG);
+        }
+
         // If we have the max number of held managed calls and we're placing an emergency call,
         // we'll disconnect the ongoing call if it cannot be held.
         if (hasMaximumManagedHoldingCalls(emergencyCall) && !canHold(liveCall)) {
@@ -4373,12 +4852,14 @@
         if (canHold(liveCall)) {
             Log.i(this, "makeRoomForOutgoingEmergencyCall: holding live call.");
             emergencyCall.getAnalytics().setCallIsAdditional(true);
+            emergencyCall.increaseHeldByThisCallCount();
             liveCall.getAnalytics().setCallIsInterrupted(true);
             liveCall.hold("calling " + emergencyCall.getId());
             return true;
         }
 
         // The live call cannot be held so we're out of luck here.  There's no room.
+        emergencyCall.setStartFailCause(CallFailureCause.CANNOT_HOLD_CALL);
         return false;
     }
 
@@ -4401,8 +4882,10 @@
         }
 
         // If the live call is stuck in a connecting state, then we should disconnect it in favor
-        // of the new outgoing call.
+        // of the new outgoing call and prompt the user to generate a bugreport.
         if (liveCall.getState() == CallState.CONNECTING) {
+            mAnomalyReporter.reportAnomaly(LIVE_CALL_STUCK_CONNECTING_ERROR_UUID,
+                    LIVE_CALL_STUCK_CONNECTING_ERROR_MSG);
             liveCall.disconnect("Force disconnect CONNECTING call.");
             return true;
         }
@@ -4418,6 +4901,7 @@
                         + " of new outgoing call.");
                 return true;
             }
+            call.setStartFailCause(CallFailureCause.MAX_OUTGOING_CALLS);
             return false;
         }
 
@@ -4466,6 +4950,7 @@
         }
 
         // The live call cannot be held so we're out of luck here.  There's no room.
+        call.setStartFailCause(CallFailureCause.CANNOT_HOLD_CALL);
         return false;
     }
 
@@ -4509,17 +4994,47 @@
         }
     }
 
+    /**
+     * Ensures that the call will be audible to the user by checking if the voice call stream is
+     * audible, and if not increasing the volume to the default value.
+     */
     private void ensureCallAudible() {
-        AudioManager am = mContext.getSystemService(AudioManager.class);
-        if (am == null) {
-            Log.w(this, "ensureCallAudible: audio manager is null");
-            return;
-        }
-        if (am.getStreamVolume(AudioManager.STREAM_VOICE_CALL) == 0) {
-            Log.i(this, "ensureCallAudible: voice call stream has volume 0. Adjusting to default.");
-            am.setStreamVolume(AudioManager.STREAM_VOICE_CALL,
-                    AudioSystem.getDefaultStreamVolume(AudioManager.STREAM_VOICE_CALL), 0);
-        }
+        // Audio manager APIs can be somewhat slow.  To prevent a potential ANR we will fire off
+        // this opreation on the async task executor.  Note that this operation does not have any
+        // dependency on any Telecom state, so we can safely launch this on a different thread
+        // without worrying that it is in the Telecom sync lock.
+        mAsyncTaskExecutor.execute(() -> {
+            AudioManager am = mContext.getSystemService(AudioManager.class);
+            if (am == null) {
+                Log.w(this, "ensureCallAudible: audio manager is null");
+                return;
+            }
+            if (am.getStreamVolume(AudioManager.STREAM_VOICE_CALL) == 0) {
+                Log.i(this,
+                        "ensureCallAudible: voice call stream has volume 0. Adjusting to default.");
+                am.setStreamVolume(AudioManager.STREAM_VOICE_CALL,
+                        AudioSystem.getDefaultStreamVolume(AudioManager.STREAM_VOICE_CALL), 0);
+            }
+        });
+    }
+
+    /**
+     * Asynchronously updates the emergency call notification.
+     * @param context the context for the update.
+     */
+    private void updateEmergencyCallNotificationAsync(Context context) {
+        mAsyncTaskExecutor.execute(() -> {
+            Log.startSession("CM.UEMCNA");
+            try {
+                boolean shouldShow = mBlockedNumbersAdapter.shouldShowEmergencyCallNotification(
+                        context);
+                Log.i(CallsManager.this, "updateEmergencyCallNotificationAsync; show=%b",
+                        shouldShow);
+                mBlockedNumbersAdapter.updateEmergencyCallNotification(context, shouldShow);
+            } finally {
+                Log.endSession();
+            }
+        });
     }
 
     /**
@@ -4567,7 +5082,7 @@
         call.setCallerDisplayName(connection.getCallerDisplayName(),
                 connection.getCallerDisplayNamePresentation());
         call.addListener(this);
-        call.putExtras(Call.SOURCE_CONNECTION_SERVICE, connection.getExtras());
+        call.putConnectionServiceExtras(connection.getExtras());
 
         Log.i(this, "createCallForExistingConnection: %s", connection);
         Call parentCall = null;
@@ -4586,6 +5101,9 @@
                 call.setParentCall(parentCall);
             }
         }
+        // Existing connections originate from a connection service, so they are completed creation
+        // by the ConnectionService implicitly.
+        call.setIsCreateConnectionComplete(true);
         addCall(call);
         if (parentCall != null) {
             // Now, set the call as a child of the parent since it has been added to Telecom.  This
@@ -4690,23 +5208,42 @@
 
     public boolean isIncomingCallPermitted(Call excludeCall,
                                            PhoneAccountHandle phoneAccountHandle) {
+        return checkIncomingCallPermitted(excludeCall, phoneAccountHandle).isSuccess();
+    }
+
+    private CallFailureCause checkIncomingCallPermitted(
+            Call call, PhoneAccountHandle phoneAccountHandle) {
         if (phoneAccountHandle == null) {
-            return false;
+            return CallFailureCause.INVALID_USE;
         }
+
         PhoneAccount phoneAccount =
                 mPhoneAccountRegistrar.getPhoneAccountUnchecked(phoneAccountHandle);
         if (phoneAccount == null) {
-            return false;
+            return CallFailureCause.INVALID_USE;
         }
-        if (isInEmergencyCall()) return false;
 
-        if (!phoneAccount.isSelfManaged()) {
-            return !hasMaximumManagedRingingCalls(excludeCall) &&
-                    !hasMaximumManagedHoldingCalls(excludeCall);
-        } else {
-            return !hasMaximumSelfManagedRingingCalls(excludeCall, phoneAccountHandle) &&
-                    !hasMaximumSelfManagedCalls(excludeCall, phoneAccountHandle);
+        if (isInEmergencyCall()) {
+            return CallFailureCause.IN_EMERGENCY_CALL;
         }
+
+        if (phoneAccount.isSelfManaged()) {
+            if (hasMaximumSelfManagedRingingCalls(call, phoneAccountHandle)) {
+                return CallFailureCause.MAX_RINGING_CALLS;
+            }
+            if (hasMaximumSelfManagedCalls(call, phoneAccountHandle)) {
+                return CallFailureCause.MAX_SELF_MANAGED_CALLS;
+            }
+        } else {
+            if (hasMaximumManagedRingingCalls(call)) {
+                return CallFailureCause.MAX_RINGING_CALLS;
+            }
+            if (hasMaximumManagedHoldingCalls(call)) {
+                return CallFailureCause.MAX_HOLD_CALLS;
+            }
+        }
+
+        return CallFailureCause.NONE;
     }
 
     public boolean isOutgoingCallPermitted(PhoneAccountHandle phoneAccountHandle) {
@@ -4878,7 +5415,7 @@
      *
      * @param pw The {@code IndentingPrintWriter} to write the state to.
      */
-    public void dump(IndentingPrintWriter pw) {
+    public void dump(IndentingPrintWriter pw, String[] args) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
         if (mCalls != null) {
             pw.println("mCalls: ");
@@ -4934,6 +5471,20 @@
             pw.decreaseIndent();
         }
 
+        if (mCallAnomalyWatchdog != null) {
+            pw.println("mCallAnomalyWatchdog:");
+            pw.increaseIndent();
+            mCallAnomalyWatchdog.dump(pw);
+            pw.decreaseIndent();
+        }
+
+        if (mEmergencyCallDiagnosticLogger != null) {
+            pw.println("mEmergencyCallDiagnosticLogger:");
+            pw.increaseIndent();
+            mEmergencyCallDiagnosticLogger.dump(pw, args);
+            pw.decreaseIndent();
+        }
+
         if (mDefaultDialerCache != null) {
             pw.println("mDefaultDialerCache:");
             pw.increaseIndent();
@@ -4966,8 +5517,9 @@
         if (call.getState() == CallState.DISCONNECTED && (isPotentialMMICode(call.getHandle())
                 || isPotentialInCallMMICode(call.getHandle())) && !mCalls.contains(call)) {
             DisconnectCause disconnectCause = call.getDisconnectCause();
-            if (!TextUtils.isEmpty(disconnectCause.getDescription()) && (disconnectCause.getCode()
-                    == DisconnectCause.ERROR)) {
+            if (!TextUtils.isEmpty(disconnectCause.getDescription()) && ((disconnectCause.getCode()
+                    == DisconnectCause.ERROR) || (disconnectCause.getCode()
+                    == DisconnectCause.RESTRICTED))) {
                 Intent errorIntent = new Intent(mContext, ErrorDialogActivity.class);
                 errorIntent.putExtra(ErrorDialogActivity.ERROR_MESSAGE_STRING_EXTRA,
                         disconnectCause.getDescription());
@@ -5039,6 +5591,9 @@
         } else {
             call.setConnectionService(service);
             service.createConnectionFailed(call);
+            if (!mCalls.contains(call)){
+                mListeners.forEach(l -> l.onCreateConnectionFailed(call));
+            }
         }
     }
 
@@ -5061,9 +5616,20 @@
         } else {
             call.setConnectionService(service);
             service.createConferenceFailed(call);
+            if (!mCalls.contains(call)){
+                mListeners.forEach(l -> l.onCreateConnectionFailed(call));
+            }
         }
     }
 
+    /**
+     * Notify interested parties that a new call is about to be handed off to a ConnectionService to
+     * be created.
+     * @param theCall the new call.
+     */
+    private void notifyStartCreateConnection(final Call theCall) {
+        mListeners.forEach(l -> l.onStartCreateConnection(theCall));
+    }
 
     /**
      * Notifies the {@link android.telecom.ConnectionService} associated with a
@@ -5227,7 +5793,7 @@
                 // Disconnect all self-managed calls to make priority for emergency call.
                 disconnectSelfManagedCalls("emergency call");
             }
-
+            notifyStartCreateConnection(call);
             call.startCreateConnection(mPhoneAccountRegistrar);
         }
 
@@ -5404,7 +5970,7 @@
         extras.putBoolean(TelecomManager.EXTRA_IS_HANDOVER_CONNECTION, true);
         extras.putParcelable(TelecomManager.EXTRA_HANDOVER_FROM_PHONE_ACCOUNT,
                 fromCall.getTargetPhoneAccount());
-
+        notifyStartCreateConnection(call);
         call.startCreateConnection(mPhoneAccountRegistrar);
     }
 
@@ -5412,8 +5978,10 @@
         return mConnectionSvrFocusMgr;
     }
 
-    private boolean canHold(Call call) {
-        return call.can(Connection.CAPABILITY_HOLD) && call.getState() != CallState.DIALING;
+    @VisibleForTesting
+    public boolean canHold(Call call) {
+        return ((call.isTransactionalCall() && call.can(Connection.CAPABILITY_SUPPORT_HOLD)) ||
+                call.can(Connection.CAPABILITY_HOLD)) && call.getState() != CallState.DIALING;
     }
 
     private boolean supportsHold(Call call) {
@@ -5435,8 +6003,12 @@
         @Override
         public void performAction() {
             synchronized (mLock) {
-                Log.d(this, "perform set call state for %s, state = %s", mCall, mState);
-                setCallState(mCall, mState, mTag);
+                Log.d(this, "performAction: current call state %s", mCall);
+                if (mCall.getState() != CallState.DISCONNECTED
+                        && mCall.getState() != CallState.DISCONNECTING) {
+                    Log.d(this, "performAction: setting to new state = %s", mState);
+                    setCallState(mCall, mState, mTag);
+                }
             }
         }
     }
@@ -5517,6 +6089,57 @@
         }
     }
 
+    /**
+     * Intended for ongoing or new calls that would like to go active/answered and need to
+     * update the mConnectionSvrFocusMgr before setting the state
+     */
+    public void transactionRequestNewFocusCall(Call call, int newCallState,
+            OutcomeReceiver<Boolean, CallException> callback) {
+        Log.d(this, "transactionRequestNewFocusCall");
+        PendingAction pendingAction = new ActionSetCallState(call, newCallState,
+                "transactional ActionSetCallState");
+        mConnectionSvrFocusMgr
+                .requestFocus(call,
+                        new TransactionalFocusRequestCallback(pendingAction, call, callback));
+    }
+
+    /**
+     * Request a new call focus and ensure the request was successful via an OutcomeReceiver. Also,
+     * include a PendingAction that will execute if the call focus change is successful.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public class TransactionalFocusRequestCallback implements
+            ConnectionServiceFocusManager.RequestFocusCallback {
+        private PendingAction mPendingAction;
+        @NonNull
+        private Call mTargetCallFocus;
+        private OutcomeReceiver<Boolean, CallException> mCallback;
+
+        TransactionalFocusRequestCallback(PendingAction pendingAction, @NonNull Call call,
+                OutcomeReceiver<Boolean, CallException> callback) {
+            mPendingAction = pendingAction;
+            mTargetCallFocus = call;
+            mCallback = callback;
+        }
+
+        @Override
+        public void onRequestFocusDone(ConnectionServiceFocusManager.CallFocus call) {
+            Call currentCallFocus = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
+            // verify the update was successful before updating the state
+            Log.i(this, "tFRC: currentCallFocus=[%s], targetFocus=[%s]",
+                    mTargetCallFocus, currentCallFocus);
+            if (currentCallFocus == null ||
+                    !currentCallFocus.getId().equals(mTargetCallFocus.getId())) {
+                mCallback.onError(new CallException("failed to switch focus to requested call",
+                        CallException.CODE_CALL_CANNOT_BE_SET_TO_ACTIVE));
+                return;
+            }
+            // at this point, we know the FocusManager is able to update successfully
+            mPendingAction.performAction(); // set the call state
+            mCallback.onResult(true); // complete the transaction
+        }
+    }
+
     public void resetConnectionTime(Call call) {
         call.setConnectTimeMillis(System.currentTimeMillis());
         call.setConnectElapsedTimeMillis(SystemClock.elapsedRealtime());
@@ -5536,7 +6159,8 @@
      * call, or a number which has been identified by the number as an emergency call.
      * @return {@code true} if there is an ongoing emergency call, {@code false} otherwise.
      */
-    public boolean isInEmergencyCall() {
+    public boolean
+    isInEmergencyCall() {
         return mCalls.stream().filter(c -> (c.isEmergencyCall()
                 || c.isNetworkIdentifiedEmergencyCall()) && !c.isDisconnected()).count() > 0;
     }
@@ -5585,6 +6209,19 @@
     }
 
     /**
+     * Determines if a {@link Call} is visible to the calling user. If the {@link PhoneAccount} has
+     * CAPABILITY_MULTI_USER, or the user handle associated with the {@link PhoneAccount} is the
+     * same as the calling user, the call is visible to the user.
+     * @param call
+     * @return {@code true} if call is visible to the calling user
+     */
+    boolean isCallVisibleForUser(Call call, UserHandle userHandle) {
+        return call.getUserHandleFromTargetPhoneAccount().equals(userHandle)
+                || call.getPhoneAccountFromHandle()
+                .hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER);
+    }
+
+    /**
      * Determines if two {@link Call} instances originated from either the same target
      * {@link PhoneAccountHandle} or connection manager {@link PhoneAccountHandle}.
      * @param call1 The first call
@@ -5665,4 +6302,22 @@
     public Ringer getRinger() {
         return mRinger;
     }
+
+    @VisibleForTesting
+    public VoipCallMonitor getVoipCallMonitor() {
+        return mVoipCallMonitor;
+    }
+
+    /**
+     * This method should only be used for testing.
+     */
+    @VisibleForTesting
+    public void createActionSetCallStateAndPerformAction(Call call, int state, String tag) {
+        ActionSetCallState actionSetCallState = new ActionSetCallState(call, state, tag);
+        actionSetCallState.performAction();
+    }
+
+    public CallStreamingController getCallStreamingController() {
+        return mCallStreamingController;
+    }
 }
diff --git a/src/com/android/server/telecom/CallsManagerListenerBase.java b/src/com/android/server/telecom/CallsManagerListenerBase.java
index 55c7b53..43f3b90 100644
--- a/src/com/android/server/telecom/CallsManagerListenerBase.java
+++ b/src/com/android/server/telecom/CallsManagerListenerBase.java
@@ -18,12 +18,14 @@
 
 import android.telecom.AudioState;
 import android.telecom.CallAudioState;
+import android.telecom.CallEndpoint;
 import android.telecom.VideoProfile;
+import java.util.Set;
 
 /**
  * Provides a default implementation for listeners of CallsManager.
  */
-public class CallsManagerListenerBase implements CallsManager.CallsManagerListener {
+public abstract class CallsManagerListenerBase implements CallsManager.CallsManagerListener {
     @Override
     public void onCallAdded(Call call) {
     }
@@ -33,6 +35,10 @@
     }
 
     @Override
+    public void onCreateConnectionFailed(Call call) {
+    }
+
+    @Override
     public void onCallStateChanged(Call call, int oldState, int newState) {
     }
 
@@ -57,6 +63,18 @@
     }
 
     @Override
+    public void onCallEndpointChanged(CallEndpoint callEndpoint) {
+    }
+
+    @Override
+    public void onAvailableCallEndpointsChanged(Set<CallEndpoint> availableCallEndpoints) {
+    }
+
+    @Override
+    public void onMuteStateChanged(boolean isMuted) {
+    }
+
+    @Override
     public void onRingbackRequested(Call call, boolean ringback) {
     }
 
@@ -90,6 +108,10 @@
     }
 
     @Override
+    public void onCallStreamingStateChanged(Call call, boolean isStreaming) {
+    }
+
+    @Override
     public void onDisconnectedTonePlaying(boolean isTonePlaying) {
     }
 
diff --git a/src/com/android/server/telecom/CarModeTracker.java b/src/com/android/server/telecom/CarModeTracker.java
index 737ce5a..ae8febf 100644
--- a/src/com/android/server/telecom/CarModeTracker.java
+++ b/src/com/android/server/telecom/CarModeTracker.java
@@ -150,7 +150,9 @@
         Log.i(this, "handleExitCarMode: packageName=%s, priority=%d", packageName, priority);
         mCarModeChangeLog.log("exitCarMode: packageName=" + packageName + ", priority="
                 + priority);
-        mCarModeApps.removeIf(c -> c.getPriority() == priority);
+
+        //Remove the car mode app with specified priority without clearing out the projection entry.
+        mCarModeApps.removeIf(c -> c.getPriority() == priority && !c.hasSetAutomotiveProjection());
     }
 
     public void handleSetAutomotiveProjection(@NonNull String packageName) {
diff --git a/src/com/android/server/telecom/ConnectionServiceFocusManager.java b/src/com/android/server/telecom/ConnectionServiceFocusManager.java
index aa0a64f..8db98e9 100644
--- a/src/com/android/server/telecom/ConnectionServiceFocusManager.java
+++ b/src/com/android/server/telecom/ConnectionServiceFocusManager.java
@@ -154,7 +154,8 @@
     }
 
     private static final int[] PRIORITY_FOCUS_CALL_STATE = new int[] {
-            CallState.ACTIVE, CallState.CONNECTING, CallState.DIALING, CallState.AUDIO_PROCESSING
+            CallState.ACTIVE, CallState.CONNECTING, CallState.DIALING, CallState.AUDIO_PROCESSING,
+            CallState.RINGING
     };
 
     private static final int MSG_REQUEST_FOCUS = 1;
@@ -348,13 +349,14 @@
     public List<CallFocus> getAllCall() { return mCalls; }
 
     private void updateConnectionServiceFocus(ConnectionServiceFocus connSvrFocus) {
+        Log.i(this, "updateConnectionServiceFocus connSvr = %s", connSvrFocus);
         if (!Objects.equals(mCurrentFocus, connSvrFocus)) {
             if (connSvrFocus != null) {
                 connSvrFocus.setConnectionServiceFocusListener(mConnectionServiceFocusListener);
                 connSvrFocus.connectionServiceFocusGained();
             }
             mCurrentFocus = connSvrFocus;
-            Log.d(this, "updateConnectionServiceFocus connSvr = %s", connSvrFocus);
+            Log.i(this, "updateConnectionServiceFocus connSvr = %s", connSvrFocus);
         }
     }
 
@@ -362,6 +364,7 @@
         mCurrentFocusCall = null;
 
         if (mCurrentFocus == null) {
+            Log.d(this, "updateCurrentFocusCall: mCurrentFocus is null");
             return;
         }
 
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
old mode 100755
new mode 100644
index 5bb1dbe..cc5932c
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -23,15 +23,21 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.location.Location;
+import android.location.LocationManager;
+import android.location.LocationRequest;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
+import android.os.CancellationSignal;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
+import android.os.Process;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.os.UserHandle;
 import android.telecom.CallAudioState;
-import android.telecom.CallScreeningService;
+import android.telecom.CallEndpoint;
 import android.telecom.Connection;
 import android.telecom.ConnectionRequest;
 import android.telecom.ConnectionService;
@@ -42,11 +48,15 @@
 import android.telecom.ParcelableConference;
 import android.telecom.ParcelableConnection;
 import android.telecom.PhoneAccountHandle;
+import android.telecom.QueryLocationException;
 import android.telecom.StatusHints;
 import android.telecom.TelecomManager;
 import android.telecom.VideoProfile;
 import android.telephony.CellIdentity;
 import android.telephony.TelephonyManager;
+import android.util.Pair;
+
+import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telecom.IConnectionService;
@@ -61,7 +71,11 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
 import java.util.Objects;
 
 /**
@@ -75,6 +89,9 @@
         ConnectionServiceFocusManager.ConnectionServiceFocus {
 
     private static final String TELECOM_ABBREVIATION = "cast";
+    private CompletableFuture<Pair<Integer, Location>> mQueryLocationFuture = null;
+    private @Nullable CancellationSignal mOngoingQueryLocationRequest = null;
+    private final ExecutorService mQueryLocationExecutor = Executors.newSingleThreadExecutor();
 
     private final class Adapter extends IConnectionServiceAdapter.Stub {
 
@@ -358,9 +375,8 @@
                         if (call.isAlive() && !call.isDisconnectHandledViaFuture()) {
                             mCallsManager.markCallAsDisconnected(
                                     call, new DisconnectCause(DisconnectCause.REMOTE));
-                        } else {
-                            mCallsManager.markCallAsRemoved(call);
                         }
+                        mCallsManager.markCallAsRemoved(call);
                     }
                 }
             } catch (Throwable t) {
@@ -435,7 +451,13 @@
                             childCall.setParentAndChildCall(null);
                         } else {
                             Call conferenceCall = mCallIdMapper.getCall(conferenceCallId);
-                            childCall.setParentAndChildCall(conferenceCall);
+                            // In a situation where a cmgr is used, the conference should be tracked
+                            // by that cmgr's instance of CSW. The cmgr instance of CSW will track
+                            // and properly set the parent and child calls so the request from the
+                            // original Telephony instance of CSW can be ignored.
+                            if (conferenceCall != null){
+                                childCall.setParentAndChildCall(conferenceCall);
+                            }
                         }
                     } else {
                         // Log.w(this, "setIsConferenced, unknown call id: %s", args.arg1);
@@ -724,6 +746,26 @@
         }
 
         @Override
+        public void requestCallEndpointChange(String callId, CallEndpoint endpoint,
+                ResultReceiver callback, Session.Info sessionInfo) {
+            Log.startSession(sessionInfo, "CSW.rCEC", mPackageAbbreviation);
+            long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    logIncoming("requestCallEndpointChange %s %s", callId,
+                            endpoint.getEndpointName());
+                    mCallsManager.requestCallEndpointChange(endpoint, callback);
+                }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
+            } finally {
+                Binder.restoreCallingIdentity(token);
+                Log.endSession();
+            }
+        }
+
+        @Override
         public void setStatusHints(String callId, StatusHints statusHints,
                 Session.Info sessionInfo) {
             Log.startSession(sessionInfo, "CSW.sSH", mPackageAbbreviation);
@@ -754,7 +796,7 @@
                     Bundle.setDefusable(extras, true);
                     Call call = mCallIdMapper.getCall(callId);
                     if (call != null) {
-                        call.putExtras(Call.SOURCE_CONNECTION_SERVICE, extras);
+                        call.putConnectionServiceExtras(extras);
                     }
                 }
             } catch (Throwable t) {
@@ -888,7 +930,7 @@
                     // an emergency call.
                             mPhoneAccountRegistrar.getCallCapablePhoneAccounts(null /*uriScheme*/,
                             false /*includeDisabledAccounts*/, userHandle, 0 /*capabilities*/,
-                            0 /*excludedCapabilities*/);
+                            0 /*excludedCapabilities*/, false);
                     PhoneAccountHandle phoneAccountHandle = null;
                     for (PhoneAccountHandle accountHandle : accountHandles) {
                         if(accountHandle.equals(callingPhoneAccountHandle)) {
@@ -1196,6 +1238,71 @@
                 Log.endSession();
             }
         }
+
+        @Override
+        public void queryLocation(String callId, long timeoutMillis, String provider,
+                ResultReceiver callback, Session.Info sessionInfo) {
+            Log.startSession(sessionInfo, "CSW.qL", mPackageAbbreviation);
+
+            TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class);
+            if (telecomManager == null || !telecomManager.getSimCallManager().getComponentName()
+                    .equals(getComponentName())) {
+                callback.send(0 /* isSuccess */,
+                        getQueryLocationErrorResult(QueryLocationException.ERROR_NOT_PERMITTED));
+                Log.endSession();
+                return;
+            }
+
+            String opPackageName = mContext.getOpPackageName();
+            int packageUid = -1;
+            try {
+                packageUid = mContext.getPackageManager().getPackageUid(opPackageName,
+                        PackageManager.PackageInfoFlags.of(0));
+            } catch (PackageManager.NameNotFoundException e) {
+                // packageUid is -1
+            }
+
+            try {
+                mAppOpsManager.noteProxyOp(
+                        AppOpsManager.OPSTR_FINE_LOCATION,
+                        opPackageName,
+                        packageUid,
+                        null,
+                        null);
+            } catch (SecurityException e) {
+                Log.e(ConnectionServiceWrapper.this, e, "");
+            }
+
+            if (!callingUidMatchesPackageManagerRecords(getComponentName().getPackageName())) {
+                throw new SecurityException(String.format("queryCurrentLocation: "
+                                + "uid mismatch found : callingPackageName=[%s], callingUid=[%d]",
+                        getComponentName().getPackageName(), Binder.getCallingUid()));
+            }
+
+            Call call = mCallIdMapper.getCall(callId);
+            if (call == null || !call.isEmergencyCall()) {
+                callback.send(0 /* isSuccess */,
+                        getQueryLocationErrorResult(QueryLocationException
+                                .ERROR_NOT_ALLOWED_FOR_NON_EMERGENCY_CONNECTIONS));
+                Log.endSession();
+                return;
+            }
+
+            long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    logIncoming("queryLocation %s %d", callId, timeoutMillis);
+                    ConnectionServiceWrapper.this.queryCurrentLocation(timeoutMillis, provider,
+                            callback);
+                }
+            } catch (Throwable t) {
+                Log.e(ConnectionServiceWrapper.this, t, "");
+                throw t;
+            } finally {
+                Binder.restoreCallingIdentity(token);
+                Log.endSession();
+            }
+        }
     }
 
     private final Adapter mAdapter = new Adapter();
@@ -1222,7 +1329,8 @@
      * @param context The context.
      * @param userHandle The {@link UserHandle} to use when binding.
      */
-    ConnectionServiceWrapper(
+    @VisibleForTesting
+    public ConnectionServiceWrapper(
             ComponentName componentName,
             ConnectionServiceRepository connectionServiceRepository,
             PhoneAccountRegistrar phoneAccountRegistrar,
@@ -1282,6 +1390,141 @@
         return null;
     }
 
+    @VisibleForTesting
+    @SuppressWarnings("FutureReturnValueIgnored")
+    public void queryCurrentLocation(long timeoutMillis, String provider, ResultReceiver callback) {
+
+        if (mQueryLocationFuture != null && !mQueryLocationFuture.isDone()) {
+            callback.send(0 /* isSuccess */,
+                    getQueryLocationErrorResult(
+                            QueryLocationException.ERROR_PREVIOUS_REQUEST_EXISTS));
+            return;
+        }
+
+        LocationManager locationManager = (LocationManager) mContext.createAttributionContext(
+                ConnectionServiceWrapper.class.getSimpleName()).getSystemService(
+                Context.LOCATION_SERVICE);
+
+        if (locationManager == null) {
+            callback.send(0 /* isSuccess */,
+                    getQueryLocationErrorResult(QueryLocationException.ERROR_SERVICE_UNAVAILABLE));
+        }
+
+        mQueryLocationFuture = new CompletableFuture<Pair<Integer, Location>>()
+                .completeOnTimeout(
+                        Pair.create(QueryLocationException.ERROR_REQUEST_TIME_OUT, null),
+                        timeoutMillis, TimeUnit.MILLISECONDS);
+
+        mOngoingQueryLocationRequest = new CancellationSignal();
+        locationManager.getCurrentLocation(
+                provider,
+                new LocationRequest.Builder(0)
+                        .setQuality(LocationRequest.QUALITY_HIGH_ACCURACY)
+                        .setLocationSettingsIgnored(true)
+                        .build(),
+                mOngoingQueryLocationRequest,
+                mQueryLocationExecutor,
+                (location) -> mQueryLocationFuture.complete(Pair.create(null, location)));
+
+        mQueryLocationFuture.whenComplete((result, e) -> {
+            if (e != null) {
+                callback.send(0,
+                        getQueryLocationErrorResult(QueryLocationException.ERROR_UNSPECIFIED));
+            }
+            //make sure we don't pass mock locations diretly, always reset() mock locations
+            if (result.second != null) {
+                if(result.second.isMock()) {
+                    result.second.reset();
+                }
+                callback.send(1, getQueryLocationResult(result.second));
+            } else {
+                callback.send(0, getQueryLocationErrorResult(result.first));
+            }
+
+            if (mOngoingQueryLocationRequest != null) {
+                mOngoingQueryLocationRequest.cancel();
+                mOngoingQueryLocationRequest = null;
+            }
+
+            if (mQueryLocationFuture != null) {
+                mQueryLocationFuture = null;
+            }
+        });
+    }
+
+    private Bundle getQueryLocationResult(Location location) {
+        Bundle extras = new Bundle();
+        extras.putParcelable(Connection.EXTRA_KEY_QUERY_LOCATION, location);
+        return extras;
+    }
+
+    private Bundle getQueryLocationErrorResult(int result) {
+        String message;
+
+        switch (result) {
+            case QueryLocationException.ERROR_REQUEST_TIME_OUT:
+                message = "The operation was not completed on time";
+                break;
+            case QueryLocationException.ERROR_PREVIOUS_REQUEST_EXISTS:
+                message = "The operation was rejected due to a previous request exists";
+                break;
+            case QueryLocationException.ERROR_NOT_PERMITTED:
+                message = "The operation is not permitted";
+                break;
+            case QueryLocationException.ERROR_NOT_ALLOWED_FOR_NON_EMERGENCY_CONNECTIONS:
+                message = "Non-emergency call connection are not allowed";
+                break;
+            case QueryLocationException.ERROR_SERVICE_UNAVAILABLE:
+                message = "The operation has failed due to service is not available";
+                break;
+            default:
+                message = "The operation has failed due to an unknown or unspecified error";
+        }
+
+        QueryLocationException exception = new QueryLocationException(message, result);
+        Bundle extras = new Bundle();
+        extras.putParcelable(QueryLocationException.QUERY_LOCATION_ERROR, exception);
+        return extras;
+    }
+
+    /**
+     * helper method that compares the binder_uid to what the packageManager_uid reports for the
+     * passed in packageName.
+     *
+     * returns true if the binder_uid matches the packageManager_uid records
+     */
+    private boolean callingUidMatchesPackageManagerRecords(String packageName) {
+        int packageUid = -1;
+        int callingUid = Binder.getCallingUid();
+
+        PackageManager pm;
+        try{
+            pm = mContext.createContextAsUser(
+                    UserHandle.getUserHandleForUid(callingUid), 0).getPackageManager();
+        }
+        catch (Exception e){
+            Log.i(this, "callingUidMatchesPackageManagerRecords:"
+                    + " createContextAsUser hit exception=[%s]", e.toString());
+            return false;
+        }
+
+        if (pm != null) {
+            try {
+                packageUid = pm.getPackageUid(packageName, PackageManager.PackageInfoFlags.of(0));
+            } catch (PackageManager.NameNotFoundException e) {
+                // packageUid is -1.
+            }
+        }
+
+        if (packageUid != callingUid) {
+            Log.i(this, "callingUidMatchesPackageManagerRecords: uid mismatch found for "
+                    + "packageName=[%s]. packageManager reports packageUid=[%d] but "
+                    + "binder reports callingUid=[%d]", packageName, packageUid, callingUid);
+        }
+
+        return packageUid == callingUid;
+    }
+
     /**
      * Creates a conference for a new outgoing call or attach to an existing incoming call.
      */
@@ -1357,7 +1600,7 @@
             public void onSuccess() {
                 String callId = mCallIdMapper.getCallId(call);
                 if (callId == null) {
-                    Log.w(ConnectionServiceWrapper.this, "Call not present"
+                    Log.i(ConnectionServiceWrapper.this, "Call not present"
                             + " in call id mapper, maybe it was aborted before the bind"
                             + " completed successfully?");
                     response.handleCreateConnectionFailure(
@@ -1674,6 +1917,54 @@
         }
     }
 
+    /** @see IConnectionService#onCallEndpointChanged(String, CallEndpoint, Session.Info) */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void onCallEndpointChanged(Call activeCall, CallEndpoint callEndpoint) {
+        final String callId = mCallIdMapper.getCallId(activeCall);
+        if (callId != null && isServiceValid("onCallEndpointChanged")) {
+            try {
+                logOutgoing("onCallEndpointChanged %s %s", callId, callEndpoint);
+                mServiceInterface.onCallEndpointChanged(callId, callEndpoint,
+                        Log.getExternalSession(TELECOM_ABBREVIATION));
+            } catch (RemoteException e) {
+                Log.d(this, "Remote exception calling onCallEndpointChanged");
+            }
+        }
+    }
+
+    /** @see IConnectionService#onAvailableCallEndpointsChanged(String, List, Session.Info) */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void onAvailableCallEndpointsChanged(Call activeCall,
+            Set<CallEndpoint> availableCallEndpoints) {
+        final String callId = mCallIdMapper.getCallId(activeCall);
+        if (callId != null && isServiceValid("onAvailableCallEndpointsChanged")) {
+            try {
+                logOutgoing("onAvailableCallEndpointsChanged %s", callId);
+                List<CallEndpoint> availableEndpoints = new ArrayList<>(availableCallEndpoints);
+                mServiceInterface.onAvailableCallEndpointsChanged(callId, availableEndpoints,
+                        Log.getExternalSession(TELECOM_ABBREVIATION));
+            } catch (RemoteException e) {
+                Log.d(this,
+                        "Remote exception calling onAvailableCallEndpointsChanged");
+            }
+        }
+    }
+
+    /** @see IConnectionService#onMuteStateChanged(String, boolean, Session.Info) */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void onMuteStateChanged(Call activeCall, boolean isMuted) {
+        final String callId = mCallIdMapper.getCallId(activeCall);
+        if (callId != null && isServiceValid("onMuteStateChanged")) {
+            try {
+                logOutgoing("onMuteStateChanged %s %s", callId, isMuted);
+                mServiceInterface.onMuteStateChanged(callId, isMuted,
+                        Log.getExternalSession(TELECOM_ABBREVIATION));
+            } catch (RemoteException e) {
+                Log.d(this, "Remote exception calling onMuteStateChanged");
+            }
+        }
+    }
+
     /** @see IConnectionService#onUsingAlternativeUi(String, boolean, Session.Info) */
     @VisibleForTesting
     public void onUsingAlternativeUi(Call activeCall, boolean isUsingAlternativeUi) {
diff --git a/src/com/android/server/telecom/CreateConnectionProcessor.java b/src/com/android/server/telecom/CreateConnectionProcessor.java
index 3561211..331c32b 100644
--- a/src/com/android/server/telecom/CreateConnectionProcessor.java
+++ b/src/com/android/server/telecom/CreateConnectionProcessor.java
@@ -16,8 +16,10 @@
 
 package com.android.server.telecom;
 
+import android.Manifest;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.os.UserHandle;
 import android.telecom.DisconnectCause;
 import android.telecom.Log;
 import android.telecom.ParcelableConference;
@@ -389,12 +391,23 @@
             // current user.
             // ONLY include phone accounts which are NOT self-managed; we will never consider a self
             // managed phone account for placing an emergency call.
+            UserHandle userFromCall = mCall.getUserHandleFromTargetPhoneAccount();
             List<PhoneAccount> allAccounts = mPhoneAccountRegistrar
-                    .getAllPhoneAccountsOfCurrentUser()
+                    .getAllPhoneAccounts(userFromCall, false)
                     .stream()
                     .filter(act -> !act.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED))
                     .collect(Collectors.toList());
 
+            if (allAccounts.isEmpty()) {
+                // Try using phone accounts from other users to place the call (i.e. using an
+                // available work sim) given that the current user has the INTERACT_ACROSS_USERS
+                // permission.
+                allAccounts = mPhoneAccountRegistrar.getAllPhoneAccounts(userFromCall, true)
+                        .stream()
+                        .filter(act -> !act.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED))
+                        .collect(Collectors.toList());
+            }
+
             if (allAccounts.isEmpty() && mContext.getPackageManager().hasSystemFeature(
                     PackageManager.FEATURE_TELEPHONY)) {
                 // If the list of phone accounts is empty at this point, it means Telephony hasn't
diff --git a/src/com/android/server/telecom/DefaultDialerCache.java b/src/com/android/server/telecom/DefaultDialerCache.java
index a4a0242..3ce394e 100644
--- a/src/com/android/server/telecom/DefaultDialerCache.java
+++ b/src/com/android/server/telecom/DefaultDialerCache.java
@@ -265,7 +265,7 @@
             if (packageName == null ||
                     Objects.equals(packageName, mCurrentDefaultDialerPerUser.get(userId))) {
                 String newDefaultDialer = refreshCacheForUser(userId);
-                Log.i(LOG_TAG, "Refreshing default dialer for user %d: now %s",
+                Log.v(LOG_TAG, "Refreshing default dialer for user %d: now %s",
                         userId, newDefaultDialer);
             }
         }
diff --git a/src/com/android/server/telecom/EmergencyCallDiagnosticLogger.java b/src/com/android/server/telecom/EmergencyCallDiagnosticLogger.java
new file mode 100644
index 0000000..af79da3
--- /dev/null
+++ b/src/com/android/server/telecom/EmergencyCallDiagnosticLogger.java
@@ -0,0 +1,438 @@
+/*
+ * Copyright (C) 2022 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.server.telecom;
+
+import static android.telephony.TelephonyManager.EmergencyCallDiagnosticParams;
+
+import android.os.BugreportManager;
+import android.os.DropBoxManager;
+import android.provider.DeviceConfig;
+import android.telecom.DisconnectCause;
+import android.telecom.Log;
+import android.telephony.TelephonyManager;
+import android.util.LocalLog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+
+/**
+ * The EmergencyCallDiagnosticsLogger monitors information required to diagnose potential outgoing
+ * ecall failures on the device. When a potential failure is detected, it calls a Telephony API to
+ * persist relevant information (dumpsys, logcat etc.) to the dropbox. This acts as a central place
+ * to determine when and what to collect.
+ *
+ * <p>When a bugreport is triggered, this module will read the dropbox entries and add them to the
+ * telecom dump.
+ */
+public class EmergencyCallDiagnosticLogger extends CallsManagerListenerBase
+        implements Call.Listener {
+
+    public static final int REPORT_REASON_RANGE_START = -1; //!!DO NOT CHANGE
+    public static final int REPORT_REASON_RANGE_END = 5; //increment this and add new reason above
+    public static final int COLLECTION_TYPE_BUGREPORT = 10;
+    public static final int COLLECTION_TYPE_TELECOM_STATE = 11;
+    public static final int COLLECTION_TYPE_TELEPHONY_STATE = 12;
+    public static final int COLLECTION_TYPE_LOGCAT_BUFFERS = 13;
+    private static final int REPORT_REASON_STUCK_CALL_DETECTED = 0;
+    private static final int REPORT_REASON_INACTIVE_CALL_TERMINATED_BY_USER_AFTER_DELAY = 1;
+    private static final int REPORT_REASON_CALL_FAILED = 2;
+    private static final int REPORT_REASON_CALL_CREATED_BUT_NEVER_ADDED = 3;
+    private static final int REPORT_REASON_SHORT_DURATION_AFTER_GOING_ACTIVE = 4;
+    private static final String DROPBOX_TAG = "ecall_diagnostic_data";
+    private static final String ENABLE_BUGREPORT_COLLECTION_FOR_EMERGENCY_CALL_DIAGNOSTICS =
+            "enable_bugreport_collection_for_emergency_call_diagnostics";
+    private static final String ENABLE_TELECOM_DUMP_COLLECTION_FOR_EMERGENCY_CALL_DIAGNOSTICS =
+            "enable_telecom_dump_collection_for_emergency_call_diagnostics";
+
+    private static final String ENABLE_LOGCAT_COLLECTION_FOR_EMERGENCY_CALL_DIAGNOSTICS =
+            "enable_logcat_collection_for_emergency_call_diagnostics";
+    private static final String ENABLE_TELEPHONY_DUMP_COLLECTION_FOR_EMERGENCY_CALL_DIAGNOSTICS =
+            "enable_telephony_dump_collection_for_emergency_call_diagnostics";
+
+    private static final String DUMPSYS_ARG_FOR_DIAGNOSTICS = "EmergencyDiagnostics";
+
+    // max text size to read from dropbox entry
+    private static final int DEFAULT_MAX_READ_BYTES_PER_DROP_BOX_ENTRY = 500000;
+    private static final String MAX_BYTES_PER_DROP_BOX_ENTRY = "max_bytes_per_dropbox_entry";
+    private static final int MAX_DROPBOX_ENTRIES_TO_DUMP = 6;
+
+    private final Timeouts.Adapter mTimeoutAdapter;
+    // This map holds all calls, but keeps pruning non-emergency calls when we can determine it
+    private final Map<Call, CallEventTimestamps> mEmergencyCallsMap = new ConcurrentHashMap<>(2);
+    private final DropBoxManager mDropBoxManager;
+    private final LocalLog mLocalLog = new LocalLog(10);
+    private final TelephonyManager mTelephonyManager;
+    private final BugreportManager mBugreportManager;
+    private final Executor mAsyncTaskExecutor;
+    private final ClockProxy mClockProxy;
+
+    public EmergencyCallDiagnosticLogger(
+            TelephonyManager tm,
+            BugreportManager brm,
+            Timeouts.Adapter timeoutAdapter, DropBoxManager dropBoxManager,
+            Executor asyncTaskExecutor, ClockProxy clockProxy) {
+        mTimeoutAdapter = timeoutAdapter;
+        mDropBoxManager = dropBoxManager;
+        mTelephonyManager = tm;
+        mBugreportManager = brm;
+        mAsyncTaskExecutor = asyncTaskExecutor;
+        mClockProxy = clockProxy;
+    }
+
+    // this calculates time from ACTIVE --> removed
+    private static long getCallTimeInActiveStateSec(CallEventTimestamps ts) {
+        if (ts.getCallActiveTime() == 0 || ts.getCallRemovedTime() == 0) {
+            return 0;
+        } else {
+            return (ts.getCallRemovedTime() - ts.getCallActiveTime()) / 1000;
+        }
+    }
+
+    // this calculates time from call created --> removed
+    private static long getTotalCallTimeSec(CallEventTimestamps ts) {
+        if (ts.getCallRemovedTime() == 0 || ts.getCallCreatedTime() == 0) {
+            return 0;
+        } else {
+            return (ts.getCallRemovedTime() - ts.getCallCreatedTime()) / 1000;
+        }
+    }
+
+    //determines what to collect based on fail reason
+    //if COLLECTION_TYPE_BUGREPORT is present in the returned list, then that
+    //should be the only collection type in the list
+    @VisibleForTesting
+    public static List<Integer> getDataCollectionTypes(int reason) {
+        switch (reason) {
+            case REPORT_REASON_SHORT_DURATION_AFTER_GOING_ACTIVE:
+                return Arrays.asList(COLLECTION_TYPE_TELECOM_STATE);
+            case REPORT_REASON_CALL_CREATED_BUT_NEVER_ADDED:
+                return Arrays.asList(
+                        COLLECTION_TYPE_TELECOM_STATE, COLLECTION_TYPE_TELEPHONY_STATE);
+            case REPORT_REASON_CALL_FAILED:
+            case REPORT_REASON_INACTIVE_CALL_TERMINATED_BY_USER_AFTER_DELAY:
+            case REPORT_REASON_STUCK_CALL_DETECTED:
+                return Arrays.asList(
+                        COLLECTION_TYPE_TELECOM_STATE,
+                        COLLECTION_TYPE_TELEPHONY_STATE,
+                        COLLECTION_TYPE_LOGCAT_BUFFERS);
+            default:
+        }
+        return new ArrayList<>();
+    }
+
+    private int getMaxBytesPerDropboxEntry() {
+        return DeviceConfig.getInt(DeviceConfig.NAMESPACE_TELEPHONY,
+                MAX_BYTES_PER_DROP_BOX_ENTRY, DEFAULT_MAX_READ_BYTES_PER_DROP_BOX_ENTRY);
+    }
+
+    @VisibleForTesting
+    public Map<Call, CallEventTimestamps> getEmergencyCallsMap() {
+        return mEmergencyCallsMap;
+    }
+
+    private void triggerDiagnosticsCollection(Call call, int reason) {
+        Log.i(this, "Triggering diagnostics for call %s reason: %d", call.getId(), reason);
+        List<Integer> dataCollectionTypes = getDataCollectionTypes(reason);
+        boolean invokeTelephonyPersistApi = false;
+        CallEventTimestamps ts = mEmergencyCallsMap.get(call);
+        EmergencyCallDiagnosticParams dp =
+                new EmergencyCallDiagnosticParams();
+        for (Integer dataCollectionType : dataCollectionTypes) {
+            switch (dataCollectionType) {
+                case COLLECTION_TYPE_TELECOM_STATE:
+                    if (isTelecomDumpCollectionEnabled()) {
+                        dp.setTelecomDumpSysCollection(true);
+                        invokeTelephonyPersistApi = true;
+                    }
+                    break;
+                case COLLECTION_TYPE_TELEPHONY_STATE:
+                    if (isTelephonyDumpCollectionEnabled()) {
+                        dp.setTelephonyDumpSysCollection(true);
+                        invokeTelephonyPersistApi = true;
+                    }
+                    break;
+                case COLLECTION_TYPE_LOGCAT_BUFFERS:
+                    if (isLogcatCollectionEnabled()) {
+                        dp.setLogcatCollection(true, ts.getCallCreatedTime());
+                        invokeTelephonyPersistApi = true;
+                    }
+                    break;
+                case COLLECTION_TYPE_BUGREPORT:
+                    if (isBugreportCollectionEnabled()) {
+                        mAsyncTaskExecutor.execute(new Runnable() {
+                            @Override
+                            public void run() {
+                                persistBugreport();
+                            }
+                        });
+                    }
+                    break;
+                default:
+            }
+        }
+        if (invokeTelephonyPersistApi) {
+            mAsyncTaskExecutor.execute(new Runnable() {
+                @Override
+                public void run() {
+                    Log.i(this, "Requesting Telephony to persist data %s", dp.toString());
+                    try {
+                        mTelephonyManager.persistEmergencyCallDiagnosticData(DROPBOX_TAG, dp);
+                    } catch (Exception e) {
+                        Log.w(this,
+                                "Exception while invoking "
+                                        + "Telephony#persistEmergencyCallDiagnosticData  %s",
+                                e.toString());
+                    }
+                }
+            });
+        }
+    }
+
+    private boolean isBugreportCollectionEnabled() {
+        return DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_TELEPHONY,
+                ENABLE_BUGREPORT_COLLECTION_FOR_EMERGENCY_CALL_DIAGNOSTICS,
+                false);
+    }
+
+    private boolean isTelecomDumpCollectionEnabled() {
+        return DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_TELEPHONY,
+                ENABLE_TELECOM_DUMP_COLLECTION_FOR_EMERGENCY_CALL_DIAGNOSTICS,
+                true);
+    }
+
+    private boolean isLogcatCollectionEnabled() {
+        return DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_TELEPHONY,
+                ENABLE_LOGCAT_COLLECTION_FOR_EMERGENCY_CALL_DIAGNOSTICS,
+                true);
+    }
+
+    private boolean isTelephonyDumpCollectionEnabled() {
+        return DeviceConfig.getBoolean(
+                DeviceConfig.NAMESPACE_TELEPHONY,
+                ENABLE_TELEPHONY_DUMP_COLLECTION_FOR_EMERGENCY_CALL_DIAGNOSTICS,
+                true);
+    }
+
+    private void persistBugreport() {
+        if (isBugreportCollectionEnabled()) {
+            // TODO:
+        }
+    }
+
+    private boolean shouldTrackCall(Call call) {
+        return (call != null && call.isEmergencyCall() && call.isOutgoing());
+    }
+
+    public void reportStuckCall(Call call) {
+        if (shouldTrackCall(call)) {
+            Log.i(this, "Triggering diagnostics for stuck call %s", call.getId());
+            triggerDiagnosticsCollection(call, REPORT_REASON_STUCK_CALL_DETECTED);
+            call.removeListener(this);
+            mEmergencyCallsMap.remove(call);
+        }
+    }
+
+    @Override
+    public void onStartCreateConnection(Call call) {
+        if (shouldTrackCall(call)) {
+            long currentTime = mClockProxy.currentTimeMillis();
+            call.addListener(this);
+            Log.i(this, "Tracking call %s timestamp: %d", call.getId(), currentTime);
+            mEmergencyCallsMap.put(call, new CallEventTimestamps(currentTime));
+        }
+    }
+
+    @Override
+    public void onCreateConnectionFailed(Call call) {
+        if (shouldTrackCall(call)) {
+            Log.i(this, "Triggering diagnostics for  call %s that was never added", call.getId());
+            triggerDiagnosticsCollection(call, REPORT_REASON_CALL_CREATED_BUT_NEVER_ADDED);
+            call.removeListener(this);
+            mEmergencyCallsMap.remove(call);
+        }
+    }
+
+    /**
+     * Override of {@link CallsManagerListenerBase} to track when calls are removed
+     *
+     * @param call the call
+     */
+    @Override
+    public void onCallRemoved(Call call) {
+        if (call != null && (mEmergencyCallsMap.get(call) != null)) {
+            call.removeListener(this);
+
+            CallEventTimestamps ts = mEmergencyCallsMap.get(call);
+            long currentTime = mClockProxy.currentTimeMillis();
+            ts.setCallRemovedTime(currentTime);
+
+            maybeTriggerDiagnosticsCollection(call, ts);
+            mEmergencyCallsMap.remove(call);
+        }
+    }
+
+    // !NOTE!: this method should only be called after we get onCallRemoved
+    private void maybeTriggerDiagnosticsCollection(Call removedCall, CallEventTimestamps ts) {
+        Log.i(this, "Evaluating emergency call for diagnostic logging: %s", removedCall.getId());
+        boolean wentActive = (ts.getCallActiveTime() != 0);
+        long callActiveTimeSec = (wentActive ? getCallTimeInActiveStateSec(ts) : 0);
+        long timeSinceCallCreatedSec = getTotalCallTimeSec(ts);
+        int dc = removedCall.getDisconnectCause().getCode();
+
+        if (wentActive) {
+            if (callActiveTimeSec
+                    < mTimeoutAdapter.getEmergencyCallActiveTimeThresholdMillis() / 1000) {
+                // call connected but did not go on for long
+                triggerDiagnosticsCollection(
+                        removedCall, REPORT_REASON_SHORT_DURATION_AFTER_GOING_ACTIVE);
+            }
+        } else {
+
+            if (dc == DisconnectCause.LOCAL
+                    && timeSinceCallCreatedSec
+                    > mTimeoutAdapter.getEmergencyCallTimeBeforeUserDisconnectThresholdMillis()
+                    / 1000) {
+                // call was disconnected by the user (but not immediately)
+                triggerDiagnosticsCollection(
+                        removedCall, REPORT_REASON_INACTIVE_CALL_TERMINATED_BY_USER_AFTER_DELAY);
+            } else if (dc != DisconnectCause.LOCAL) {
+                // this can be a case for a full bugreport
+                triggerDiagnosticsCollection(removedCall, REPORT_REASON_CALL_FAILED);
+            }
+        }
+    }
+
+    /**
+     * Override of {@link com.android.server.telecom.CallsManager.CallsManagerListener} to track
+     * call state changes.
+     *
+     * @param call     the call
+     * @param oldState its old state
+     * @param newState the new state
+     */
+    @Override
+    public void onCallStateChanged(Call call, int oldState, int newState) {
+
+        if (call != null && mEmergencyCallsMap.get(call) != null && newState == CallState.ACTIVE) {
+            CallEventTimestamps ts = mEmergencyCallsMap.get(call);
+            if (ts != null) {
+                long currentTime = mClockProxy.currentTimeMillis();
+                ts.setCallActiveTime(currentTime);
+            }
+        }
+    }
+
+    private void dumpDiagnosticDataFromDropbox(IndentingPrintWriter pw) {
+        pw.increaseIndent();
+        pw.println("PERSISTED DIAGNOSTIC DATA FROM DROP BOX");
+        int totalEntriesDumped = 0;
+        long currentTime = mClockProxy.currentTimeMillis();
+        long entriesAfterTime =
+                currentTime - (mTimeoutAdapter.getDaysBackToSearchEmergencyDiagnosticEntries() * 24
+                        * 60L * 60L * 1000L);
+        Log.i(this, "current time: %d entriesafter: %d", currentTime, entriesAfterTime);
+        DropBoxManager.Entry entry;
+        entry = mDropBoxManager.getNextEntry(DROPBOX_TAG, entriesAfterTime);
+        while (entry != null) {
+            Log.i(this, "found entry with ts: %d", entry.getTimeMillis());
+            String content[] = entry.getText(getMaxBytesPerDropboxEntry()).split(
+                    System.lineSeparator());
+            long entryTime = entry.getTimeMillis();
+            if (content != null) {
+                pw.increaseIndent();
+                pw.println("------------BEGIN ENTRY (" + entryTime + ")--------");
+                for (String line : content) {
+                    pw.println(line);
+                }
+                pw.println("--------END ENTRY--------");
+                pw.decreaseIndent();
+                totalEntriesDumped++;
+            }
+            entry = mDropBoxManager.getNextEntry(DROPBOX_TAG, entryTime);
+            if (totalEntriesDumped > MAX_DROPBOX_ENTRIES_TO_DUMP) {
+                /*
+                Since Emergency calls are a rare/once in a lifetime time occurrence for most users,
+                we should not be seeing too many entries. This code just guards against edge case
+                like load testing, b2b failures etc. We may accumulate a lot of dropbox entries in
+                such cases, but we limit to dumping only MAX_DROPBOX_ENTRIES_TO_DUMP in the
+                bugreport
+
+                The Dropbox API in its current state does not allow to query Entries in reverse
+                chronological order efficiently.
+                 */
+
+                Log.i(this, "Skipping dump for remaining entries. dumped :%d", totalEntriesDumped);
+                break;
+            }
+        }
+        pw.println("END OF PERSISTED DIAGNOSTIC DATA FROM DROP BOX");
+        pw.decreaseIndent();
+    }
+
+    public void dump(IndentingPrintWriter pw, String[] args) {
+        pw.increaseIndent();
+        mLocalLog.dump(pw);
+        pw.decreaseIndent();
+        if (args != null && args.length > 0 && args[0].equals(DUMPSYS_ARG_FOR_DIAGNOSTICS)) {
+            //dont read dropbox entries since this dump is triggered by telephony for diagnostics
+            Log.i(this, "skipped dumping diagnostic data");
+            return;
+        }
+        dumpDiagnosticDataFromDropbox(pw);
+    }
+
+    private static class CallEventTimestamps {
+
+        private final long mCallCreatedTime;
+        private long mCallActiveTime;
+        private long mCallRemovedTime;
+
+        public CallEventTimestamps(long createdTime) {
+            mCallCreatedTime = createdTime;
+        }
+
+        public long getCallActiveTime() {
+            return mCallActiveTime;
+        }
+
+        public void setCallActiveTime(long callActiveTime) {
+            this.mCallActiveTime = callActiveTime;
+        }
+
+        public long getCallCreatedTime() {
+            return mCallCreatedTime;
+        }
+
+        public long getCallRemovedTime() {
+            return mCallRemovedTime;
+        }
+
+        public void setCallRemovedTime(long callRemovedTime) {
+            this.mCallRemovedTime = callRemovedTime;
+        }
+    }
+}
diff --git a/src/com/android/server/telecom/EmergencyCallHelper.java b/src/com/android/server/telecom/EmergencyCallHelper.java
index 5de4e5a..a213e26 100644
--- a/src/com/android/server/telecom/EmergencyCallHelper.java
+++ b/src/com/android/server/telecom/EmergencyCallHelper.java
@@ -34,8 +34,17 @@
     private final DefaultDialerCache mDefaultDialerCache;
     private final Timeouts.Adapter mTimeoutsAdapter;
     private UserHandle mLocationPermissionGrantedToUser;
+
+    //stores the original state of permissions that dialer had
     private boolean mHadFineLocation = false;
     private boolean mHadBackgroundLocation = false;
+
+    //stores whether we successfully granted the runtime permission
+    //This is stored so we don't unnecessarily revoke if the grant had failed with an exception.
+    //Else we will get an exception
+    private boolean mFineLocationGranted= false;
+    private boolean mBackgroundLocationGranted = false;
+
     private long mLastEmergencyCallTimestampMillis;
 
     @VisibleForTesting
@@ -48,7 +57,8 @@
         mTimeoutsAdapter = timeoutsAdapter;
     }
 
-    void maybeGrantTemporaryLocationPermission(Call call, UserHandle userHandle) {
+    @VisibleForTesting
+    public void maybeGrantTemporaryLocationPermission(Call call, UserHandle userHandle) {
         if (shouldGrantTemporaryLocationPermission(call)) {
             grantLocationPermission(userHandle);
         }
@@ -57,7 +67,8 @@
         }
     }
 
-    void maybeRevokeTemporaryLocationPermission() {
+    @VisibleForTesting
+    public void maybeRevokeTemporaryLocationPermission() {
         if (wasGrantedTemporaryLocationPermission()) {
             revokeLocationPermission();
         }
@@ -95,51 +106,65 @@
 
     private void grantLocationPermission(UserHandle userHandle) {
         String systemDialerPackage = mDefaultDialerCache.getSystemDialerApplication();
-        Log.i(this, "Granting temporary location permission to " + systemDialerPackage
-              + ", user: " + userHandle);
-        try {
-            boolean hadBackgroundLocation = hasBackgroundLocationPermission();
-            boolean hadFineLocation = hasFineLocationPermission();
-            if (hadBackgroundLocation && hadFineLocation) {
-                Log.i(this, "Skipping location grant because the system dialer already"
-                        + " holds sufficient permissions");
-                return;
-            }
-            if (!hadFineLocation) {
+        Log.i(this, "Attempting to grant temporary location permission to " + systemDialerPackage
+            + ", user: " + userHandle);
+
+        boolean hadBackgroundLocation = hasBackgroundLocationPermission();
+        boolean hadFineLocation = hasFineLocationPermission();
+        if (hadBackgroundLocation && hadFineLocation) {
+            Log.i(this, "Skipping location grant because the system dialer already"
+                + " holds sufficient permissions");
+            return;
+        }
+        mHadFineLocation = hadFineLocation;
+        mHadBackgroundLocation = hadBackgroundLocation;
+
+        if (!hadFineLocation) {
+            try {
                 mContext.getPackageManager().grantRuntimePermission(systemDialerPackage,
-                        Manifest.permission.ACCESS_FINE_LOCATION, userHandle);
+                    Manifest.permission.ACCESS_FINE_LOCATION, userHandle);
+                recordFineLocationPermissionGrant(userHandle);
+            } catch (Exception e) {
+                Log.i(this, "Failed to grant ACCESS_FINE_LOCATION");
             }
-            if (!hadBackgroundLocation) {
+        }
+        if (!hadBackgroundLocation) {
+            try {
                 mContext.getPackageManager().grantRuntimePermission(systemDialerPackage,
-                        Manifest.permission.ACCESS_BACKGROUND_LOCATION, userHandle);
+                    Manifest.permission.ACCESS_BACKGROUND_LOCATION, userHandle);
+                recordBackgroundLocationPermissionGrant(userHandle);
+            } catch (Exception e) {
+                Log.i(this, "Failed to grant ACCESS_BACKGROUND_LOCATION");
             }
-            mHadFineLocation = hadFineLocation;
-            mHadBackgroundLocation = hadBackgroundLocation;
-            recordPermissionGrant(userHandle);
-        } catch (Exception e) {
-            Log.e(this, e, "Failed to grant location permissions to " + systemDialerPackage
-                  + ", user: " + userHandle);
         }
     }
 
     private void revokeLocationPermission() {
         String systemDialerPackage = mDefaultDialerCache.getSystemDialerApplication();
         Log.i(this, "Revoking temporary location permission from " + systemDialerPackage
-              + ", user: " + mLocationPermissionGrantedToUser);
+            + ", user: " + mLocationPermissionGrantedToUser);
         UserHandle userHandle = mLocationPermissionGrantedToUser;
+
         try {
-            if (!mHadFineLocation) {
+            if (!mHadFineLocation && mFineLocationGranted) {
                 mContext.getPackageManager().revokeRuntimePermission(systemDialerPackage,
-                        Manifest.permission.ACCESS_FINE_LOCATION, userHandle);
-            }
-            if (!mHadBackgroundLocation) {
-                mContext.getPackageManager().revokeRuntimePermission(systemDialerPackage,
-                        Manifest.permission.ACCESS_BACKGROUND_LOCATION, userHandle);
+                    Manifest.permission.ACCESS_FINE_LOCATION, userHandle);
             }
         } catch (Exception e) {
             Log.e(this, e, "Failed to revoke location permission from " + systemDialerPackage
-                  + ", user: " + userHandle);
+                + ", user: " + userHandle);
         }
+
+        try {
+            if (!mHadBackgroundLocation && mBackgroundLocationGranted) {
+                mContext.getPackageManager().revokeRuntimePermission(systemDialerPackage,
+                    Manifest.permission.ACCESS_BACKGROUND_LOCATION, userHandle);
+            }
+        } catch (Exception e) {
+            Log.e(this, e, "Failed to revoke location permission from " + systemDialerPackage
+                + ", user: " + userHandle);
+        }
+
         clearPermissionGrant();
     }
 
@@ -157,8 +182,14 @@
                 == PackageManager.PERMISSION_GRANTED;
     }
 
-    private void recordPermissionGrant(UserHandle userHandle) {
+    private void recordBackgroundLocationPermissionGrant(UserHandle userHandle) {
         mLocationPermissionGrantedToUser = userHandle;
+        mBackgroundLocationGranted = true;
+    }
+
+    private void recordFineLocationPermissionGrant(UserHandle userHandle) {
+        mLocationPermissionGrantedToUser = userHandle;
+        mFineLocationGranted = true;
     }
 
     private boolean wasGrantedTemporaryLocationPermission() {
@@ -169,5 +200,7 @@
         mLocationPermissionGrantedToUser = null;
         mHadBackgroundLocation = false;
         mHadFineLocation = false;
+        mBackgroundLocationGranted = false;
+        mFineLocationGranted = false;
     }
 }
diff --git a/src/com/android/server/telecom/HeadsetMediaButton.java b/src/com/android/server/telecom/HeadsetMediaButton.java
index b1471c2..8e9caff 100644
--- a/src/com/android/server/telecom/HeadsetMediaButton.java
+++ b/src/com/android/server/telecom/HeadsetMediaButton.java
@@ -23,11 +23,16 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.telecom.CallAudioState;
+import android.telecom.CallEndpoint;
 import android.telecom.Log;
+import android.util.ArraySet;
 import android.view.KeyEvent;
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.util.Set;
+
 /**
  * Static class to handle listening to the headset media buttons.
  */
@@ -67,6 +72,11 @@
             mMediaSession.setActive(active);
         }
 
+        @Override
+        public void setCallback(MediaSession.Callback callback) {
+            mMediaSession.setCallback(callback);
+        }
+
         /**
          * Gets the underlying {@link MediaSession} active status.
          * @return {@code true} if active, {@code false} otherwise.
@@ -84,6 +94,7 @@
      */
     public interface MediaSessionAdapter {
         void setActive(boolean active);
+        void setCallback(MediaSession.Callback callback);
         boolean isActive();
     }
 
@@ -143,8 +154,10 @@
     private final Context mContext;
     private final CallsManager mCallsManager;
     private final TelecomSystem.SyncRoot mLock;
+    private final Set<Call> mCalls = new ArraySet<>();
     private MediaSessionAdapter mSession;
     private KeyEvent mLastHookEvent;
+    private @CallEndpoint.EndpointType int mCurrentEndpointType;
 
     /**
      * Constructor used for testing purposes to initialize a {@link HeadsetMediaButton} with a
@@ -165,6 +178,8 @@
         mCallsManager = callsManager;
         mLock = lock;
         mSession = adapter;
+
+        adapter.setCallback(mSessionCallback);
     }
 
     /**
@@ -204,7 +219,7 @@
             return mCallsManager.onMediaButton(LONG_PRESS);
         } else if (event.getAction() == KeyEvent.ACTION_UP) {
             // We should not judge SHORT_PRESS by ACTION_UP event repeatCount, because it always
-            // return 0.
+            // returns 0.
             // Actually ACTION_DOWN event repeatCount only increases when LONG_PRESS performed.
             if (mLastHookEvent != null && mLastHookEvent.getRepeatCount() == 0) {
                 return mCallsManager.onMediaButton(SHORT_PRESS);
@@ -218,52 +233,72 @@
         return true;
     }
 
+    @Override
+    public void onCallEndpointChanged(CallEndpoint callEndpoint) {
+        mCurrentEndpointType = callEndpoint.getEndpointType();
+        Log.i(this, "onCallEndpointChanged: endPoint=%s", callEndpoint);
+        maybeChangeSessionState();
+    }
+
     /** ${inheritDoc} */
     @Override
     public void onCallAdded(Call call) {
-        if (call.isExternalCall()) {
-            return;
-        }
-        handleCallAddition();
+        handleCallAddition(call);
     }
 
     /**
      * Triggers session activation due to call addition.
      */
-    private void handleCallAddition() {
-        mMediaSessionHandler.obtainMessage(MSG_MEDIA_SESSION_SET_ACTIVE, 1, 0).sendToTarget();
-    }
-
-    /** ${inheritDoc} */
-    @Override
-    public void onCallRemoved(Call call) {
-        if (call.isExternalCall()) {
-            return;
-        }
-        handleCallRemoval();
+    private void handleCallAddition(Call call) {
+        mCalls.add(call);
+        maybeChangeSessionState();
     }
 
     /**
-     * Triggers session deactivation due to call removal.
+     * Based on whether there are tracked calls and the audio is routed to a wired headset,
+     * potentially activate or deactive the media session.
      */
-    private void handleCallRemoval() {
-        if (!mCallsManager.hasAnyCalls()) {
+    private void maybeChangeSessionState() {
+        boolean hasNonExternalCalls = !mCalls.isEmpty()
+                && mCalls.stream().anyMatch(c -> !c.isExternalCall());
+        if (hasNonExternalCalls && mCurrentEndpointType == CallEndpoint.TYPE_WIRED_HEADSET) {
+            Log.i(this, "maybeChangeSessionState: hasCalls=%b, currentEndpointType=%s, ACTIVATE",
+                    hasNonExternalCalls, CallEndpoint.endpointTypeToString(mCurrentEndpointType));
+            mMediaSessionHandler.obtainMessage(MSG_MEDIA_SESSION_SET_ACTIVE, 1, 0).sendToTarget();
+        } else {
+            Log.i(this, "maybeChangeSessionState: hasCalls=%b, currentEndpointType=%s, DEACTIVATE",
+                    hasNonExternalCalls, CallEndpoint.endpointTypeToString(mCurrentEndpointType));
             mMediaSessionHandler.obtainMessage(MSG_MEDIA_SESSION_SET_ACTIVE, 0, 0).sendToTarget();
         }
     }
 
     /** ${inheritDoc} */
     @Override
-    public void onExternalCallChanged(Call call, boolean isExternalCall) {
-        // Note: We don't use the onCallAdded/onCallRemoved methods here since they do checks to see
-        // if the call is external or not and would skip the session activation/deactivation.
-        if (isExternalCall) {
-            handleCallRemoval();
-        } else {
-            handleCallAddition();
+    public void onCallRemoved(Call call) {
+        handleCallRemoval(call);
+    }
+
+    /**
+     * Triggers session deactivation due to call removal.
+     */
+    private void handleCallRemoval(Call call) {
+        // If we were tracking the call, potentially change session state.
+        if (mCalls.remove(call)) {
+            if (mCalls.isEmpty()) {
+                // When there are no calls, don't cache that we previously had a wired headset
+                // connected; we'll be updated on the next call.
+                mCurrentEndpointType = CallEndpoint.TYPE_UNKNOWN;
+            }
+            maybeChangeSessionState();
         }
     }
 
+    /** ${inheritDoc} */
+    @Override
+    public void onExternalCallChanged(Call call, boolean isExternalCall) {
+        maybeChangeSessionState();
+    }
+
     @VisibleForTesting
     /**
      * @return the handler this class instance uses for operation; used for unit testing.
diff --git a/src/com/android/server/telecom/InCallAdapter.java b/src/com/android/server/telecom/InCallAdapter.java
index 0fda5f8..9ce10bd 100755
--- a/src/com/android/server/telecom/InCallAdapter.java
+++ b/src/com/android/server/telecom/InCallAdapter.java
@@ -19,6 +19,8 @@
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
+import android.os.ResultReceiver;
+import android.telecom.CallEndpoint;
 import android.telecom.Log;
 import android.telecom.PhoneAccountHandle;
 
@@ -396,6 +398,23 @@
     }
 
     @Override
+    public void requestCallEndpointChange(CallEndpoint endpoint, ResultReceiver callback) {
+        try {
+            Log.startSession(LogUtils.Sessions.ICA_SET_AUDIO_ROUTE, mOwnerPackageAbbreviation);
+            long token = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    mCallsManager.requestCallEndpointChange(endpoint, callback);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        } finally {
+            Log.endSession();
+        }
+    }
+
+    @Override
     public void enterBackgroundAudioProcessing(String callId) {
         try {
             Log.startSession(LogUtils.Sessions.ICA_ENTER_AUDIO_PROCESSING,
@@ -602,7 +621,9 @@
                 synchronized (mLock) {
                     Call call = mCallIdMapper.getCall(callId);
                     if (call != null) {
-                        call.putExtras(Call.SOURCE_INCALL_SERVICE, extras);
+                        // Make sure to identify the ICS that originated the extras change so that
+                        // InCallController can propagate these out to other ICSes.
+                        call.putInCallServiceExtras(extras, mOwnerPackageName);
                     } else {
                         Log.w(this, "putExtras, unknown call id: %s", callId);
                     }
@@ -674,7 +695,7 @@
     @Override
     public void sendRttRequest(String callId) {
         try {
-            Log.startSession("ICA.sRR");
+            Log.startSession("ICA.sRR", mOwnerPackageAbbreviation);
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
@@ -696,7 +717,7 @@
     @Override
     public void respondToRttRequest(String callId, int id, boolean accept) {
         try {
-            Log.startSession("ICA.rTRR");
+            Log.startSession("ICA.rTRR", mOwnerPackageAbbreviation);
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
@@ -718,7 +739,7 @@
     @Override
     public void stopRtt(String callId) {
         try {
-            Log.startSession("ICA.sRTT");
+            Log.startSession("ICA.sRTT", mOwnerPackageAbbreviation);
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
@@ -740,11 +761,16 @@
     @Override
     public void setRttMode(String callId, int mode) {
         try {
-            Log.startSession("ICA.sRM");
+            Log.startSession("ICA.sRM", mOwnerPackageAbbreviation);
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
-                    // TODO
+                    Call call = mCallIdMapper.getCall(callId);
+                    if (call != null) {
+                        call.setRttMode(mode);
+                    } else {
+                        Log.w(this, "setRttMode(): call %s not found", callId);
+                    }
                 }
             } finally {
                 Binder.restoreCallingIdentity(token);
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 9a53575..2fc59fa 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -23,11 +23,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AppOpsManager;
-import android.app.compat.CompatChanges;
 import android.app.Notification;
 import android.app.NotificationManager;
-import android.compat.annotation.ChangeId;
-import android.compat.annotation.EnabledSince;
 import android.content.AttributionSource;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -41,7 +38,6 @@
 import android.content.pm.ServiceInfo;
 import android.hardware.SensorPrivacyManager;
 import android.os.Binder;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -52,6 +48,7 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.telecom.CallAudioState;
+import android.telecom.CallEndpoint;
 import android.telecom.ConnectionService;
 import android.telecom.InCallService;
 import android.telecom.Log;
@@ -78,6 +75,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
@@ -91,6 +89,21 @@
         AppOpsManager.OnOpActiveChangedListener {
     public static final String NOTIFICATION_TAG = InCallController.class.getSimpleName();
     public static final int IN_CALL_SERVICE_NOTIFICATION_ID = 3;
+    private AnomalyReporterAdapter mAnomalyReporter = new AnomalyReporterAdapterImpl();
+
+    /**
+     * Anomaly Report UUIDs and corresponding error descriptions specific to InCallController.
+     */
+    public static final UUID SET_IN_CALL_ADAPTER_ERROR_UUID =
+            UUID.fromString("0c2adf96-353a-433c-afe9-1e5564f304f9");
+    public static final String SET_IN_CALL_ADAPTER_ERROR_MSG =
+            "Exception thrown while setting the in-call adapter.";
+
+    @VisibleForTesting
+    public void setAnomalyReporterAdapter(AnomalyReporterAdapter mAnomalyReporterAdapter){
+        mAnomalyReporter = mAnomalyReporterAdapter;
+    }
+
     public class InCallServiceConnection {
         /**
          * Indicates that a call to {@link #connect(Call)} has succeeded and resulted in a
@@ -285,6 +298,8 @@
 
         @Override
         public int connect(Call call) {
+            UserHandle userFromCall = getUserFromCall(call);
+
             if (mIsConnected) {
                 Log.addEvent(call, LogUtils.Events.INFO, "Already connected, ignoring request: "
                         + mInCallServiceInfo);
@@ -294,7 +309,7 @@
 
                     // Notify this new added call
                     sendCallToService(call, mInCallServiceInfo,
-                            mInCallServices.get(mInCallServiceInfo));
+                            mInCallServices.get(userFromCall).get(mInCallServiceInfo));
                 }
                 return CONNECTION_SUCCEEDED;
             }
@@ -320,11 +335,11 @@
             Log.i(this, "Attempting to bind to InCall %s, with %s", mInCallServiceInfo, intent);
             mIsConnected = true;
             mInCallServiceInfo.setBindingStartTime(mClockProxy.elapsedRealtime());
+            UserHandle userToBind = getUserFromCall(call);
             if (!mContext.bindServiceAsUser(intent, mServiceConnection,
                     Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
                         | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS
-                        | Context.BIND_SCHEDULE_LIKE_TOP_APP,
-                    UserHandle.CURRENT)) {
+                        | Context.BIND_SCHEDULE_LIKE_TOP_APP, userToBind)) {
                 Log.w(this, "Failed to connect.");
                 mIsConnected = false;
             }
@@ -345,6 +360,7 @@
         @Override
         public void disconnect() {
             if (mIsConnected) {
+                UserHandle userFromCall = getUserFromCall(mCall);
                 mInCallServiceInfo.setDisconnectTime(mClockProxy.elapsedRealtime());
                 Log.i(InCallController.this, "ICSBC#disconnect: unbinding after %s ms;"
                                 + "%s. isCrashed: %s", mInCallServiceInfo.mDisconnectTime
@@ -357,7 +373,7 @@
                     // Non-UI InCallServices are allowed to return null from onBind if they don't
                     // want to handle calls at the moment, so don't report them to the user as
                     // crashed.
-                    sendCrashedInCallServiceNotification(packageName);
+                    sendCrashedInCallServiceNotification(packageName, userFromCall);
                 }
                 if (mCall != null) {
                     mCall.getAnalytics().addInCallService(
@@ -368,7 +384,7 @@
                     updateCallTracking(mCall, mInCallServiceInfo, false /* isAdd */);
                 }
 
-                InCallController.this.onDisconnected(mInCallServiceInfo);
+                InCallController.this.onDisconnected(mInCallServiceInfo, userFromCall);
             } else {
                 Log.i(InCallController.this, "ICSBC#disconnect: already disconnected; %s",
                         mInCallServiceInfo);
@@ -394,7 +410,8 @@
 
         protected void onConnected(IBinder service) {
             boolean shouldRemainConnected =
-                    InCallController.this.onConnected(mInCallServiceInfo, service);
+                    InCallController.this.onConnected(mInCallServiceInfo, service,
+                            getUserFromCall(mCall));
             if (!shouldRemainConnected) {
                 // Sometimes we can opt to disconnect for certain reasons, like if the
                 // InCallService rejected our initialization step, or the calls went away
@@ -405,11 +422,25 @@
         }
 
         protected void onDisconnected() {
-            InCallController.this.onDisconnected(mInCallServiceInfo);
+            boolean shouldReconnect = mIsConnected;
+            InCallController.this.onDisconnected(mInCallServiceInfo, getUserFromCall(mCall));
             disconnect();  // Unbind explicitly if we get disconnected.
             if (mListener != null) {
                 mListener.onDisconnect(InCallServiceBindingConnection.this, mCall);
             }
+            // Check if we are expected to reconnect
+            if (shouldReconnect && shouldHandleReconnect()) {
+                connect(mCall);  // reconnect
+            }
+        }
+
+        private boolean shouldHandleReconnect() {
+            int serviceType = mInCallServiceInfo.getType();
+            boolean nonUI = (serviceType == IN_CALL_SERVICE_TYPE_NON_UI)
+                    || (serviceType == IN_CALL_SERVICE_TYPE_COMPANION);
+            boolean carModeUI = (serviceType == IN_CALL_SERVICE_TYPE_CAR_MODE_UI);
+
+            return carModeUI || (nonUI && !mIsNullBinding);
         }
     }
 
@@ -462,9 +493,9 @@
                 // Could not connect to child, stop proxying.
                 mIsProxying = false;
             }
-
+            UserHandle userFromCall = getUserFromCall(call);
             mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(call,
-                    mCallsManager.getCurrentUserHandle());
+                    userFromCall);
 
             if (call != null && call.isIncoming()
                     && mEmergencyCallHelper.getLastEmergencyCallTimeMillis() > 0) {
@@ -472,7 +503,7 @@
                 Bundle extras = new Bundle();
                 extras.putLong(android.telecom.Call.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS,
                         mEmergencyCallHelper.getLastEmergencyCallTimeMillis());
-                call.putExtras(Call.SOURCE_CONNECTION_SERVICE, extras);
+                call.putConnectionServiceExtras(extras);
             }
 
             // If we are here, we didn't or could not connect to child. So lets connect ourselves.
@@ -612,13 +643,13 @@
          *
          * @param packageName The package name of the car mode app.
          */
-        public synchronized void changeCarModeApp(String packageName) {
+        public synchronized void changeCarModeApp(String packageName, UserHandle userHandle) {
             Log.i(this, "changeCarModeApp: isCarModeNow=" + mIsCarMode);
 
             InCallServiceInfo currentConnectionInfo = mCurrentConnection == null ? null
                     : mCurrentConnection.getInfo();
             InCallServiceInfo carModeConnectionInfo =
-                    getInCallServiceComponent(packageName,
+                    getInCallServiceComponent(userHandle, packageName,
                             IN_CALL_SERVICE_TYPE_CAR_MODE_UI, true /* ignoreDisabed */);
 
             if (!Objects.equals(currentConnectionInfo, carModeConnectionInfo)
@@ -787,7 +818,7 @@
 
         @Override
         public void onConnectionPropertiesChanged(Call call, boolean didRttChange) {
-            updateCall(call, false /* includeVideoProvider */, didRttChange);
+            updateCall(call, false /* includeVideoProvider */, didRttChange, null);
         }
 
         @Override
@@ -797,7 +828,7 @@
 
         @Override
         public void onVideoCallProviderChanged(Call call) {
-            updateCall(call, true /* videoProviderChanged */, false);
+            updateCall(call, true /* videoProviderChanged */, false, null);
         }
 
         @Override
@@ -805,25 +836,35 @@
             updateCall(call);
         }
 
+        @Override
+        public void onCallerInfoChanged(Call call) {
+            updateCall(call);
+        }
+
         /**
          * Listens for changes to extras reported by a Telecom {@link Call}.
          *
          * Extras changes can originate from a {@link ConnectionService} or an {@link InCallService}
-         * so we will only trigger an update of the call information if the source of the extras
-         * change was a {@link ConnectionService}.
+         * so we will only trigger an update of the call information if the source of the
+         * extras change was a {@link ConnectionService}.
          *
-         * @param call The call.
-         * @param source The source of the extras change ({@link Call#SOURCE_CONNECTION_SERVICE} or
+         * @param call   The call.
+         * @param source The source of the extras change
+         *               ({@link Call#SOURCE_CONNECTION_SERVICE} or
          *               {@link Call#SOURCE_INCALL_SERVICE}).
          * @param extras The extras.
          */
         @Override
-        public void onExtrasChanged(Call call, int source, Bundle extras) {
-            // Do not inform InCallServices of changes which originated there.
-            if (source == Call.SOURCE_INCALL_SERVICE) {
-                return;
+        public void onExtrasChanged(Call call, int source, Bundle extras,
+                String requestingPackageName) {
+            if (source == Call.SOURCE_CONNECTION_SERVICE) {
+                updateCall(call);
+            } else if (source == Call.SOURCE_INCALL_SERVICE && requestingPackageName != null) {
+                // If the change originated from another InCallService, we'll propagate the change
+                // to all other InCallServices running, EXCEPT the one who made the original change.
+                updateCall(call, false /* videoProviderChanged */, false /* rttInfoChanged */,
+                        requestingPackageName);
             }
-            updateCall(call);
         }
 
         /**
@@ -894,7 +935,7 @@
         @Override
         public void onRttInitiationFailure(Call call, int reason) {
             notifyRttInitiationFailure(call, reason);
-            updateCall(call, false, true);
+            updateCall(call, false, true, null);
         }
 
         @Override
@@ -917,6 +958,8 @@
                 if (Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction())) {
                     synchronized (mLock) {
                         String changedPackage = intent.getData().getSchemeSpecificPart();
+                        int uid = intent.getIntExtra(Intent.EXTRA_UID, 0);
+                        UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
                         List<InCallServiceBindingConnection> componentsToBind =
                                 Arrays.stream(intent.getStringArrayExtra(
                                         Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST))
@@ -925,13 +968,14 @@
                                                         className))
                                         .filter(mKnownNonUiInCallServices::contains)
                                         .flatMap(componentName -> getInCallServiceComponents(
-                                                componentName,
+                                                userHandle, componentName,
                                                 IN_CALL_SERVICE_TYPE_NON_UI).stream())
                                         .map(InCallServiceBindingConnection::new)
                                         .collect(Collectors.toList());
 
-                        if (mNonUIInCallServiceConnections != null) {
-                            mNonUIInCallServiceConnections.addConnections(componentsToBind);
+                        if (mNonUIInCallServiceConnections.containsKey(userHandle)) {
+                            mNonUIInCallServiceConnections.get(userHandle).
+                                    addConnections(componentsToBind);
                         }
 
                         // If the current car mode app become enabled from disabled, update
@@ -988,7 +1032,8 @@
             CallState.DISCONNECTING };
 
     /** The in-call app implementations, see {@link IInCallService}. */
-    private final Map<InCallServiceInfo, IInCallService> mInCallServices = new ArrayMap<>();
+    private final Map<UserHandle, Map<InCallServiceInfo, IInCallService>>
+            mInCallServices = new ArrayMap<>();
 
     private final CallIdMapper mCallIdMapper = new CallIdMapper(Call::getId);
 
@@ -1002,8 +1047,10 @@
     private final DefaultDialerCache mDefaultDialerCache;
     private final EmergencyCallHelper mEmergencyCallHelper;
     private final Handler mHandler = new Handler(Looper.getMainLooper());
-    private CarSwappingInCallServiceConnection mInCallServiceConnection;
-    private NonUIInCallServiceConnectionCollection mNonUIInCallServiceConnections;
+    private final Map<UserHandle, CarSwappingInCallServiceConnection>
+            mInCallServiceConnections = new ArrayMap<>();
+    private final Map<UserHandle, NonUIInCallServiceConnectionCollection>
+            mNonUIInCallServiceConnections = new ArrayMap<>();
     private final ClockProxy mClockProxy;
     private final IBinder mToken = new Binder();
 
@@ -1136,59 +1183,70 @@
 
     @Override
     public void onCallAdded(Call call) {
-        if (!isBoundAndConnectedToServices()) {
+        UserHandle userFromCall = getUserFromCall(call);
+        if (!isBoundAndConnectedToServices(userFromCall)) {
             Log.i(this, "onCallAdded: %s; not bound or connected.", call);
             // We are not bound, or we're not connected.
             bindToServices(call);
         } else {
+            InCallServiceConnection inCallServiceConnection =
+                    mInCallServiceConnections.get(userFromCall);
+
             // We are bound, and we are connected.
-            adjustServiceBindingsForEmergency();
+            adjustServiceBindingsForEmergency(userFromCall);
 
             // This is in case an emergency call is added while there is an existing call.
             mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(call,
-                    mCallsManager.getCurrentUserHandle());
+                    userFromCall);
 
             Log.i(this, "onCallAdded: %s", call);
             // Track the call if we don't already know about it.
             addCall(call);
 
-            Log.i(this, "mInCallServiceConnection isConnected=%b",
-                    mInCallServiceConnection.isConnected());
+            if (inCallServiceConnection != null) {
+                Log.i(this, "mInCallServiceConnection isConnected=%b",
+                        inCallServiceConnection.isConnected());
+            }
 
             List<ComponentName> componentsUpdated = new ArrayList<>();
-            for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
-                InCallServiceInfo info = entry.getKey();
+            if (mInCallServices.containsKey(userFromCall)) {
+                for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.
+                        get(userFromCall).entrySet()) {
+                    InCallServiceInfo info = entry.getKey();
 
-                if (call.isExternalCall() && !info.isExternalCallsSupported()) {
-                    continue;
+                    if (call.isExternalCall() && !info.isExternalCallsSupported()) {
+                        continue;
+                    }
+
+                    if (call.isSelfManaged() && (!call.visibleToInCallService()
+                            || !info.isSelfManagedCallsSupported())) {
+                        continue;
+                    }
+
+                    // Only send the RTT call if it's a UI in-call service
+                    boolean includeRttCall = false;
+                    if (inCallServiceConnection != null) {
+                        includeRttCall = info.equals(inCallServiceConnection.getInfo());
+                    }
+
+                    componentsUpdated.add(info.getComponentName());
+                    IInCallService inCallService = entry.getValue();
+
+                    ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call,
+                            true /* includeVideoProvider */,
+                            mCallsManager.getPhoneAccountRegistrar(),
+                            info.isExternalCallsSupported(), includeRttCall,
+                            info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI ||
+                                    info.getType() == IN_CALL_SERVICE_TYPE_NON_UI);
+                    try {
+                        inCallService.addCall(
+                                sanitizeParcelableCallForService(info, parcelableCall));
+                        updateCallTracking(call, info, true /* isAdd */);
+                    } catch (RemoteException ignored) {
+                    }
                 }
-
-                if (call.isSelfManaged() && (!call.visibleToInCallService()
-                        || !info.isSelfManagedCallsSupported())) {
-                    continue;
-                }
-
-                // Only send the RTT call if it's a UI in-call service
-                boolean includeRttCall = false;
-                if (mInCallServiceConnection != null) {
-                    includeRttCall = info.equals(mInCallServiceConnection.getInfo());
-                }
-
-                componentsUpdated.add(info.getComponentName());
-                IInCallService inCallService = entry.getValue();
-
-                ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call,
-                        true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar(),
-                        info.isExternalCallsSupported(), includeRttCall,
-                        info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI ||
-                                info.getType() == IN_CALL_SERVICE_TYPE_NON_UI);
-                try {
-                    inCallService.addCall(sanitizeParcelableCallForService(info, parcelableCall));
-                    updateCallTracking(call, info, true /* isAdd */);
-                } catch (RemoteException ignored) {
-                }
+                Log.i(this, "Call added to components: %s", componentsUpdated);
             }
-            Log.i(this, "Call added to components: %s", componentsUpdated);
         }
     }
 
@@ -1204,7 +1262,7 @@
                 public void loggedRun() {
                     // Check again to make sure there are no active calls.
                     if (mCallsManager.getCalls().isEmpty()) {
-                        unbindFromServices();
+                        unbindFromServices(getUserFromCall(call));
 
                         mEmergencyCallHelper.maybeRevokeTemporaryLocationPermission();
                     }
@@ -1227,10 +1285,12 @@
         Log.i(this, "onExternalCallChanged: %s -> %b", call, isExternalCall);
 
         List<ComponentName> componentsUpdated = new ArrayList<>();
-        if (!isExternalCall) {
+        UserHandle userFromCall = getUserFromCall(call);
+        if (!isExternalCall && mInCallServices.containsKey(userFromCall)) {
             // The call was external but it is no longer external.  We must now add it to any
             // InCallServices which do not support external calls.
-            for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
+            for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.
+                    get(userFromCall).entrySet()) {
                 InCallServiceInfo info = entry.getKey();
 
                 if (info.isExternalCallsSupported()) {
@@ -1248,7 +1308,8 @@
                 IInCallService inCallService = entry.getValue();
 
                 // Only send the RTT call if it's a UI in-call service
-                boolean includeRttCall = info.equals(mInCallServiceConnection.getInfo());
+                boolean includeRttCall = info.equals(mInCallServiceConnections.
+                        get(userFromCall).getInfo());
 
                 ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call,
                         true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar(),
@@ -1267,35 +1328,38 @@
             // InCallServices which do not support external calls.
             // Remove the call by sending a call update indicating the call was disconnected.
             Log.i(this, "Removing external call %s", call);
-            for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
-                InCallServiceInfo info = entry.getKey();
-                if (info.isExternalCallsSupported()) {
-                    // For InCallServices which support external calls, we do not need to remove
-                    // the call.
-                    continue;
+            if (mInCallServices.containsKey(userFromCall)) {
+                for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.
+                        get(userFromCall).entrySet()) {
+                    InCallServiceInfo info = entry.getKey();
+                    if (info.isExternalCallsSupported()) {
+                        // For InCallServices which support external calls, we do not need to remove
+                        // the call.
+                        continue;
+                    }
+
+                    componentsUpdated.add(info.getComponentName());
+                    IInCallService inCallService = entry.getValue();
+
+                    ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(
+                            call,
+                            false /* includeVideoProvider */,
+                            mCallsManager.getPhoneAccountRegistrar(),
+                            false /* supportsExternalCalls */,
+                            android.telecom.Call.STATE_DISCONNECTED /* overrideState */,
+                            false /* includeRttCall */,
+                            info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI
+                                    || info.getType() == IN_CALL_SERVICE_TYPE_NON_UI
+                    );
+
+                    try {
+                        inCallService.updateCall(
+                                sanitizeParcelableCallForService(info, parcelableCall));
+                    } catch (RemoteException ignored) {
+                    }
                 }
-
-                componentsUpdated.add(info.getComponentName());
-                IInCallService inCallService = entry.getValue();
-
-                ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(
-                        call,
-                        false /* includeVideoProvider */,
-                        mCallsManager.getPhoneAccountRegistrar(),
-                        false /* supportsExternalCalls */,
-                        android.telecom.Call.STATE_DISCONNECTED /* overrideState */,
-                        false /* includeRttCall */,
-                        info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI
-                                || info.getType() == IN_CALL_SERVICE_TYPE_NON_UI
-                );
-
-                try {
-                    inCallService.updateCall(
-                            sanitizeParcelableCallForService(info, parcelableCall));
-                } catch (RemoteException ignored) {
-                }
+                Log.i(this, "External call removed from components: %s", componentsUpdated);
             }
-            Log.i(this, "External call removed from components: %s", componentsUpdated);
         }
         maybeTrackMicrophoneUse(isMuted());
     }
@@ -1321,12 +1385,63 @@
             Log.i(this, "Calling onAudioStateChanged, audioState: %s -> %s", oldCallAudioState,
                     newCallAudioState);
             maybeTrackMicrophoneUse(newCallAudioState.isMuted());
-            for (IInCallService inCallService : mInCallServices.values()) {
-                try {
-                    inCallService.onCallAudioStateChanged(newCallAudioState);
-                } catch (RemoteException ignored) {
+            mInCallServices.values().forEach(inCallServices -> {
+                for (IInCallService inCallService : inCallServices.values()) {
+                    try {
+                        inCallService.onCallAudioStateChanged(newCallAudioState);
+                    } catch (RemoteException ignored) {
+                    }
                 }
-            }
+            });
+        }
+    }
+
+    @Override
+    public void onCallEndpointChanged(CallEndpoint callEndpoint) {
+        if (!mInCallServices.isEmpty()) {
+            Log.i(this, "Calling onCallEndpointChanged");
+            mInCallServices.values().forEach(inCallServices -> {
+                for (IInCallService inCallService : inCallServices.values()) {
+                    try {
+                        inCallService.onCallEndpointChanged(callEndpoint);
+                    } catch (RemoteException ignored) {
+                        Log.d(this, "Remote exception calling onCallEndpointChanged");
+                    }
+                }
+            });
+        }
+    }
+
+    @Override
+    public void onAvailableCallEndpointsChanged(Set<CallEndpoint> availableCallEndpoints) {
+        if (!mInCallServices.isEmpty()) {
+            Log.i(this, "Calling onAvailableCallEndpointsChanged");
+            List<CallEndpoint> availableEndpoints = new ArrayList<>(availableCallEndpoints);
+            mInCallServices.values().forEach(inCallServices -> {
+                for (IInCallService inCallService : inCallServices.values()) {
+                    try {
+                        inCallService.onAvailableCallEndpointsChanged(availableEndpoints);
+                    } catch (RemoteException ignored) {
+                        Log.d(this, "Remote exception calling onAvailableCallEndpointsChanged");
+                    }
+                }
+            });
+        }
+    }
+
+    @Override
+    public void onMuteStateChanged(boolean isMuted) {
+        if (!mInCallServices.isEmpty()) {
+            Log.i(this, "Calling onMuteStateChanged");
+            mInCallServices.values().forEach(inCallServices -> {
+                for (IInCallService inCallService : inCallServices.values()) {
+                    try {
+                        inCallService.onMuteStateChanged(isMuted);
+                    } catch (RemoteException ignored) {
+                        Log.d(this, "Remote exception calling onMuteStateChanged");
+                    }
+                }
+            });
         }
     }
 
@@ -1334,19 +1449,22 @@
     public void onCanAddCallChanged(boolean canAddCall) {
         if (!mInCallServices.isEmpty()) {
             Log.i(this, "onCanAddCallChanged : %b", canAddCall);
-            for (IInCallService inCallService : mInCallServices.values()) {
-                try {
-                    inCallService.onCanAddCallChanged(canAddCall);
-                } catch (RemoteException ignored) {
+            mInCallServices.values().forEach(inCallServices -> {
+                for (IInCallService inCallService : inCallServices.values()) {
+                    try {
+                        inCallService.onCanAddCallChanged(canAddCall);
+                    } catch (RemoteException ignored) {
+                    }
                 }
-            }
+            });
         }
     }
 
     void onPostDialWait(Call call, String remaining) {
-        if (!mInCallServices.isEmpty()) {
+        UserHandle userFromCall = getUserFromCall(call);
+        if (mInCallServices.containsKey(userFromCall)) {
             Log.i(this, "Calling onPostDialWait, remaining = %s", remaining);
-            for (IInCallService inCallService : mInCallServices.values()) {
+            for (IInCallService inCallService : mInCallServices.get(userFromCall).values()) {
                 try {
                     inCallService.setPostDialWait(mCallIdMapper.getCallId(call), remaining);
                 } catch (RemoteException ignored) {
@@ -1406,6 +1524,7 @@
             }
 
             if (shouldStart) {
+                // Note, not checking return value, as this op call is merely for tracing use
                 mAppOpsManager.startOp(AppOpsManager.OP_PHONE_CALL_CAMERA, myUid(),
                         mContext.getOpPackageName(), false, null, null);
                 mSensorPrivacyManager.showSensorUseDialog(SensorPrivacyManager.Sensors.CAMERA);
@@ -1420,9 +1539,10 @@
         }
     }
 
-    void bringToForeground(boolean showDialpad) {
-        if (!mInCallServices.isEmpty()) {
-            for (IInCallService inCallService : mInCallServices.values()) {
+    @VisibleForTesting
+    public void bringToForeground(boolean showDialpad, UserHandle callingUser) {
+        if (mInCallServices.containsKey(callingUser)) {
+            for (IInCallService inCallService : mInCallServices.get(callingUser).values()) {
                 try {
                     inCallService.bringToForeground(showDialpad);
                 } catch (RemoteException ignored) {
@@ -1433,20 +1553,28 @@
         }
     }
 
-    void silenceRinger() {
-        if (!mInCallServices.isEmpty()) {
-            for (IInCallService inCallService : mInCallServices.values()) {
-                try {
-                    inCallService.silenceRinger();
-                } catch (RemoteException ignored) {
+    @VisibleForTesting
+    public Map<UserHandle, Map<InCallServiceInfo, IInCallService>> getInCallServices() {
+        return mInCallServices;
+    }
+
+    void silenceRinger(Set<UserHandle> userHandles) {
+        userHandles.forEach(userHandle -> {
+            if (mInCallServices.containsKey(userHandle)) {
+                for (IInCallService inCallService : mInCallServices.get(userHandle).values()) {
+                    try {
+                        inCallService.silenceRinger();
+                    } catch (RemoteException ignored) {
+                    }
                 }
             }
-        }
+        });
     }
 
     private void notifyConnectionEvent(Call call, String event, Bundle extras) {
-        if (!mInCallServices.isEmpty()) {
-            for (IInCallService inCallService : mInCallServices.values()) {
+        UserHandle userFromCall = getUserFromCall(call);
+        if (mInCallServices.containsKey(userFromCall)) {
+            for (IInCallService inCallService : mInCallServices.get(userFromCall).values()) {
                 try {
                     Log.i(this, "notifyConnectionEvent {Call: %s, Event: %s, Extras:[%s]}",
                             (call != null ? call.toString() : "null"),
@@ -1460,9 +1588,11 @@
     }
 
     private void notifyRttInitiationFailure(Call call, int reason) {
-        if (!mInCallServices.isEmpty()) {
-            mInCallServices.entrySet().stream()
-                    .filter((entry) -> entry.getKey().equals(mInCallServiceConnection.getInfo()))
+        UserHandle userFromCall = getUserFromCall(call);
+        if (mInCallServices.containsKey(userFromCall)) {
+            mInCallServices.get(userFromCall).entrySet().stream()
+                    .filter((entry) -> entry.getKey().equals(mInCallServiceConnections.
+                            get(userFromCall).getInfo()))
                     .forEach((entry) -> {
                         try {
                             Log.i(this, "notifyRttFailure, call %s, incall %s",
@@ -1476,9 +1606,11 @@
     }
 
     private void notifyRemoteRttRequest(Call call, int requestId) {
-        if (!mInCallServices.isEmpty()) {
-            mInCallServices.entrySet().stream()
-                    .filter((entry) -> entry.getKey().equals(mInCallServiceConnection.getInfo()))
+        UserHandle userFromCall = getUserFromCall(call);
+        if (mInCallServices.containsKey(userFromCall)) {
+            mInCallServices.get(userFromCall).entrySet().stream()
+                    .filter((entry) -> entry.getKey().equals(mInCallServiceConnections.
+                            get(userFromCall).getInfo()))
                     .forEach((entry) -> {
                         try {
                             Log.i(this, "notifyRemoteRttRequest, call %s, incall %s",
@@ -1492,8 +1624,9 @@
     }
 
     private void notifyHandoverFailed(Call call, int error) {
-        if (!mInCallServices.isEmpty()) {
-            for (IInCallService inCallService : mInCallServices.values()) {
+        UserHandle userFromCall = getUserFromCall(call);
+        if (mInCallServices.containsKey(userFromCall)) {
+            for (IInCallService inCallService : mInCallServices.get(userFromCall).values()) {
                 try {
                     inCallService.onHandoverFailed(mCallIdMapper.getCallId(call), error);
                 } catch (RemoteException ignored) {
@@ -1503,8 +1636,9 @@
     }
 
     private void notifyHandoverComplete(Call call) {
-        if (!mInCallServices.isEmpty()) {
-            for (IInCallService inCallService : mInCallServices.values()) {
+        UserHandle userFromCall = getUserFromCall(call);
+        if (mInCallServices.containsKey(userFromCall)) {
+            for (IInCallService inCallService : mInCallServices.get(userFromCall).values()) {
                 try {
                     inCallService.onHandoverComplete(mCallIdMapper.getCallId(call));
                 } catch (RemoteException ignored) {
@@ -1516,22 +1650,22 @@
     /**
      * Unbinds an existing bound connection to the in-call app.
      */
-    public void unbindFromServices() {
+    public void unbindFromServices(UserHandle userHandle) {
         try {
             mContext.unregisterReceiver(mPackageChangedReceiver);
         } catch (IllegalArgumentException e) {
             // Ignore this -- we may or may not have registered it, but when we bind, we want to
             // unregister no matter what.
         }
-        if (mInCallServiceConnection != null) {
-            mInCallServiceConnection.disconnect();
-            mInCallServiceConnection = null;
+        if (mInCallServiceConnections.containsKey(userHandle)) {
+            mInCallServiceConnections.get(userHandle).disconnect();
+            mInCallServiceConnections.remove(userHandle);
         }
-        if (mNonUIInCallServiceConnections != null) {
-            mNonUIInCallServiceConnections.disconnect();
-            mNonUIInCallServiceConnections = null;
+        if (mNonUIInCallServiceConnections.containsKey(userHandle)) {
+            mNonUIInCallServiceConnections.get(userHandle).disconnect();
+            mNonUIInCallServiceConnections.remove(userHandle);
         }
-        mInCallServices.clear();
+        mInCallServices.remove(userHandle);
     }
 
     /**
@@ -1543,9 +1677,10 @@
      */
     @VisibleForTesting
     public void bindToServices(Call call) {
-        if (mInCallServiceConnection == null) {
+        UserHandle userFromCall = getUserFromCall(call);
+        if (!mInCallServiceConnections.containsKey(userFromCall)) {
             InCallServiceConnection dialerInCall = null;
-            InCallServiceInfo defaultDialerComponentInfo = getDefaultDialerComponent();
+            InCallServiceInfo defaultDialerComponentInfo = getDefaultDialerComponent(userFromCall);
             Log.i(this, "defaultDialer: " + defaultDialerComponentInfo);
             if (defaultDialerComponentInfo != null &&
                     !defaultDialerComponentInfo.getComponentName().equals(
@@ -1554,28 +1689,30 @@
             }
             Log.i(this, "defaultDialer: " + dialerInCall);
 
-            InCallServiceInfo systemInCallInfo = getInCallServiceComponent(
+            InCallServiceInfo systemInCallInfo = getInCallServiceComponent(userFromCall,
                     mDefaultDialerCache.getSystemDialerComponent(), IN_CALL_SERVICE_TYPE_SYSTEM_UI);
             EmergencyInCallServiceConnection systemInCall =
                     new EmergencyInCallServiceConnection(systemInCallInfo, dialerInCall);
             systemInCall.setHasEmergency(mCallsManager.isInEmergencyCall());
 
             InCallServiceConnection carModeInCall = null;
-            InCallServiceInfo carModeComponentInfo = getCurrentCarModeComponent();
+            InCallServiceInfo carModeComponentInfo = getCurrentCarModeComponent(userFromCall);
             if (carModeComponentInfo != null &&
                     !carModeComponentInfo.getComponentName().equals(
                             mDefaultDialerCache.getSystemDialerComponent())) {
                 carModeInCall = new InCallServiceBindingConnection(carModeComponentInfo);
             }
 
-            mInCallServiceConnection =
-                    new CarSwappingInCallServiceConnection(systemInCall, carModeInCall);
+            mInCallServiceConnections.put(userFromCall,
+                    new CarSwappingInCallServiceConnection(systemInCall, carModeInCall));
         }
 
-        mInCallServiceConnection.chooseInitialInCallService(shouldUseCarModeUI());
+        CarSwappingInCallServiceConnection inCallServiceConnection =
+                mInCallServiceConnections.get(userFromCall);
+        inCallServiceConnection.chooseInitialInCallService(shouldUseCarModeUI());
 
         // Actually try binding to the UI InCallService.
-        if (mInCallServiceConnection.connect(call) ==
+        if (inCallServiceConnection.connect(call) ==
                 InCallServiceConnection.CONNECTION_SUCCEEDED || call.isSelfManaged()) {
             // Only connect to the non-ui InCallServices if we actually connected to the main UI
             // one, or if the call is self-managed (in which case we'd still want to keep Wear, BT,
@@ -1594,9 +1731,10 @@
         mContext.registerReceiver(mPackageChangedReceiver, packageChangedFilter);
     }
 
-    private void updateNonUiInCallServices() {
+    private void updateNonUiInCallServices(Call call) {
+        UserHandle userFromCall = getUserFromCall(call);
         List<InCallServiceInfo> nonUIInCallComponents =
-                getInCallServiceComponents(IN_CALL_SERVICE_TYPE_NON_UI);
+                getInCallServiceComponents(userFromCall, IN_CALL_SERVICE_TYPE_NON_UI);
         List<InCallServiceBindingConnection> nonUIInCalls = new LinkedList<>();
         for (InCallServiceInfo serviceInfo : nonUIInCallComponents) {
             nonUIInCalls.add(new InCallServiceBindingConnection(serviceInfo));
@@ -1605,27 +1743,28 @@
                 .getRoleManagerAdapter().getCallCompanionApps();
         if (callCompanionApps != null && !callCompanionApps.isEmpty()) {
             for (String pkg : callCompanionApps) {
-                InCallServiceInfo info = getInCallServiceComponent(pkg,
+                InCallServiceInfo info = getInCallServiceComponent(userFromCall, pkg,
                         IN_CALL_SERVICE_TYPE_COMPANION, true /* ignoreDisabled */);
                 if (info != null) {
                     nonUIInCalls.add(new InCallServiceBindingConnection(info));
                 }
             }
         }
-        mNonUIInCallServiceConnections = new NonUIInCallServiceConnectionCollection(
-                nonUIInCalls);
+        mNonUIInCallServiceConnections.put(userFromCall, new NonUIInCallServiceConnectionCollection(
+                nonUIInCalls));
     }
 
     private void connectToNonUiInCallServices(Call call) {
-        if (mNonUIInCallServiceConnections == null) {
-            updateNonUiInCallServices();
+        UserHandle userFromCall = getUserFromCall(call);
+        if (!mNonUIInCallServiceConnections.containsKey(userFromCall)) {
+            updateNonUiInCallServices(call);
         }
-        mNonUIInCallServiceConnections.connect(call);
+        mNonUIInCallServiceConnections.get(userFromCall).connect(call);
     }
 
-    private @Nullable InCallServiceInfo getDefaultDialerComponent() {
+    private @Nullable InCallServiceInfo getDefaultDialerComponent(UserHandle userHandle) {
         String defaultPhoneAppName = mDefaultDialerCache.getDefaultDialerApplication(
-                mCallsManager.getCurrentUserHandle().getIdentifier());
+                userHandle.getIdentifier());
         String systemPhoneAppName = mDefaultDialerCache.getSystemDialerApplication();
 
         Log.d(this, "getDefaultDialerComponent: defaultPhoneAppName=[%s]", defaultPhoneAppName);
@@ -1635,10 +1774,10 @@
         InCallServiceInfo defaultPhoneAppComponent =
                 (systemPhoneAppName != null && systemPhoneAppName.equals(defaultPhoneAppName)) ?
                         /* The defaultPhoneApp is also the systemPhoneApp. Get systemPhoneApp info*/
-                        getInCallServiceComponent(defaultPhoneAppName,
+                        getInCallServiceComponent(userHandle, defaultPhoneAppName,
                                 IN_CALL_SERVICE_TYPE_SYSTEM_UI, true /* ignoreDisabled */)
                         /* The defaultPhoneApp is NOT the systemPhoneApp. Get defaultPhoneApp info*/
-                        : getInCallServiceComponent(defaultPhoneAppName,
+                        : getInCallServiceComponent(userHandle, defaultPhoneAppName,
                                 IN_CALL_SERVICE_TYPE_DEFAULT_DIALER_UI, true /* ignoreDisabled */);
 
         Log.d(this, "getDefaultDialerComponent: defaultPhoneAppComponent=[%s]",
@@ -1650,13 +1789,16 @@
         return defaultPhoneAppComponent;
     }
 
-    private InCallServiceInfo getCurrentCarModeComponent() {
-        return getInCallServiceComponent(mCarModeTracker.getCurrentCarModePackage(),
+    private InCallServiceInfo getCurrentCarModeComponent(UserHandle userHandle) {
+        return getInCallServiceComponent(userHandle,
+                mCarModeTracker.getCurrentCarModePackage(),
                 IN_CALL_SERVICE_TYPE_CAR_MODE_UI, true /* ignoreDisabled */);
     }
 
-    private InCallServiceInfo getInCallServiceComponent(ComponentName componentName, int type) {
-        List<InCallServiceInfo> list = getInCallServiceComponents(componentName, type);
+    private InCallServiceInfo getInCallServiceComponent(UserHandle userHandle,
+            ComponentName componentName, int type) {
+        List<InCallServiceInfo> list = getInCallServiceComponents(userHandle,
+                componentName, type);
         if (list != null && !list.isEmpty()) {
             return list.get(0);
         } else {
@@ -1667,38 +1809,41 @@
         }
     }
 
-    private InCallServiceInfo getInCallServiceComponent(String packageName, int type,
-            boolean ignoreDisabled) {
-        List<InCallServiceInfo> list = getInCallServiceComponents(packageName, type,
-                ignoreDisabled);
+    private InCallServiceInfo getInCallServiceComponent(UserHandle userHandle,
+            String packageName, int type, boolean ignoreDisabled) {
+        List<InCallServiceInfo> list = getInCallServiceComponents(userHandle,
+                packageName, type, ignoreDisabled);
         if (list != null && !list.isEmpty()) {
             return list.get(0);
         }
         return null;
     }
 
-    private List<InCallServiceInfo> getInCallServiceComponents(int type) {
-        return getInCallServiceComponents(null, null, type);
+    private List<InCallServiceInfo> getInCallServiceComponents(
+            UserHandle userHandle, int type) {
+        return getInCallServiceComponents(userHandle, null, null, type);
     }
 
-    private List<InCallServiceInfo> getInCallServiceComponents(String packageName, int type,
-            boolean ignoreDisabled) {
-        return getInCallServiceComponents(packageName, null, type, ignoreDisabled);
+    private List<InCallServiceInfo> getInCallServiceComponents(UserHandle userHandle,
+            String packageName, int type, boolean ignoreDisabled) {
+        return getInCallServiceComponents(userHandle, packageName, null,
+                type, ignoreDisabled);
     }
 
-    private List<InCallServiceInfo> getInCallServiceComponents(ComponentName componentName,
-            int type) {
-        return getInCallServiceComponents(null, componentName, type);
+    private List<InCallServiceInfo> getInCallServiceComponents(UserHandle userHandle,
+            ComponentName componentName, int type) {
+        return getInCallServiceComponents(userHandle, null, componentName, type);
     }
 
-    private List<InCallServiceInfo> getInCallServiceComponents(String packageName,
-            ComponentName componentName, int requestedType) {
-        return getInCallServiceComponents(packageName, componentName, requestedType,
-                true /* ignoreDisabled */);
+    private List<InCallServiceInfo> getInCallServiceComponents(UserHandle userHandle,
+            String packageName, ComponentName componentName, int requestedType) {
+        return getInCallServiceComponents(userHandle, packageName,
+                componentName, requestedType, true /* ignoreDisabled */);
     }
 
-    private List<InCallServiceInfo> getInCallServiceComponents(String packageName,
-            ComponentName componentName, int requestedType, boolean ignoreDisabled) {
+    private List<InCallServiceInfo> getInCallServiceComponents(UserHandle userHandle,
+            String packageName, ComponentName componentName,
+            int requestedType, boolean ignoreDisabled) {
         List<InCallServiceInfo> retval = new LinkedList<>();
 
         Intent serviceIntent = new Intent(InCallService.SERVICE_INTERFACE);
@@ -1708,16 +1853,15 @@
         if (componentName != null) {
             serviceIntent.setComponent(componentName);
         }
-
         PackageManager packageManager = mContext.getPackageManager();
-        Context userContext = mContext.createContextAsUser(mCallsManager.getCurrentUserHandle(),
+        Context userContext = mContext.createContextAsUser(userHandle,
                 0 /* flags */);
         PackageManager userPackageManager = userContext != null ?
                 userContext.getPackageManager() : packageManager;
         for (ResolveInfo entry : packageManager.queryIntentServicesAsUser(
                 serviceIntent,
                 PackageManager.GET_META_DATA | PackageManager.MATCH_DISABLED_COMPONENTS,
-                mCallsManager.getCurrentUserHandle().getIdentifier())) {
+                userHandle.getIdentifier())) {
             ServiceInfo serviceInfo = entry.serviceInfo;
 
             if (serviceInfo != null) {
@@ -1728,8 +1872,8 @@
                         serviceInfo.metaData.getBoolean(
                                 TelecomManager.METADATA_INCLUDE_SELF_MANAGED_CALLS, false);
 
-                int currentType = getInCallServiceType(entry.serviceInfo, packageManager,
-                        packageName);
+                int currentType = getInCallServiceType(userHandle,
+                        entry.serviceInfo, packageManager, packageName);
                 ComponentName foundComponentName =
                         new ComponentName(serviceInfo.packageName, serviceInfo.name);
                 if (requestedType == IN_CALL_SERVICE_TYPE_NON_UI) {
@@ -1780,8 +1924,8 @@
     /**
      * Returns the type of InCallService described by the specified serviceInfo.
      */
-    private int getInCallServiceType(ServiceInfo serviceInfo, PackageManager packageManager,
-            String packageName) {
+    private int getInCallServiceType(UserHandle userHandle, ServiceInfo serviceInfo,
+            PackageManager packageManager, String packageName) {
         // Verify that the InCallService requires the BIND_INCALL_SERVICE permission which
         // enforces that only Telecom can bind to it.
         boolean hasServiceBindPermission = serviceInfo.permission != null &&
@@ -1833,7 +1977,7 @@
         // Check to see that it is the default dialer package
         boolean isDefaultDialerPackage = Objects.equals(serviceInfo.packageName,
                 mDefaultDialerCache.getDefaultDialerApplication(
-                    mCallsManager.getCurrentUserHandle().getIdentifier()));
+                    userHandle.getIdentifier()));
         if (isDefaultDialerPackage && isUIService) {
             return IN_CALL_SERVICE_TYPE_DEFAULT_DIALER_UI;
         }
@@ -1852,11 +1996,11 @@
         return IN_CALL_SERVICE_TYPE_INVALID;
     }
 
-    private void adjustServiceBindingsForEmergency() {
+    private void adjustServiceBindingsForEmergency(UserHandle userHandle) {
         // The connected UI is not the system UI, so lets check if we should switch them
         // if there exists an emergency number.
         if (mCallsManager.isInEmergencyCall()) {
-            mInCallServiceConnection.setHasEmergency(true);
+            mInCallServiceConnections.get(userHandle).setHasEmergency(true);
         }
     }
 
@@ -1869,7 +2013,7 @@
      * @param service The {@link IInCallService} implementation.
      * @return True if we successfully connected.
      */
-    private boolean onConnected(InCallServiceInfo info, IBinder service) {
+    private boolean onConnected(InCallServiceInfo info, IBinder service, UserHandle userHandle) {
         Log.i(this, "onConnected to %s", info.getComponentName());
 
         if (info.getType() == IN_CALL_SERVICE_TYPE_CAR_MODE_UI
@@ -1878,8 +2022,9 @@
             trackCallingUserInterfaceStarted(info);
         }
         IInCallService inCallService = IInCallService.Stub.asInterface(service);
-        mInCallServices.put(info, inCallService);
-
+        mInCallServices.putIfAbsent(userHandle,
+                new ArrayMap<InCallController.InCallServiceInfo, IInCallService>());
+        mInCallServices.get(userHandle).put(info, inCallService);
         try {
             inCallService.setInCallAdapter(
                     new InCallAdapter(
@@ -1889,6 +2034,8 @@
                             info.getComponentName().getPackageName()));
         } catch (RemoteException e) {
             Log.e(this, e, "Failed to set the in-call adapter.");
+            mAnomalyReporter.reportAnomaly(SET_IN_CALL_ADAPTER_ERROR_UUID,
+                    SET_IN_CALL_ADAPTER_ERROR_MSG);
             Trace.endSection();
             return false;
         }
@@ -1924,10 +2071,11 @@
                 return 0;
             }
 
+            UserHandle userFromCall = getUserFromCall(call);
             // Only send the RTT call if it's a UI in-call service
             boolean includeRttCall = false;
-            if (mInCallServiceConnection != null) {
-                includeRttCall = info.equals(mInCallServiceConnection.getInfo());
+            if (mInCallServiceConnections.containsKey(userFromCall)) {
+                includeRttCall = info.equals(mInCallServiceConnections.get(userFromCall).getInfo());
             }
 
             // Track the call if we don't already know about it.
@@ -1953,14 +2101,16 @@
      *
      * @param disconnectedInfo The {@link InCallServiceInfo} of the service which disconnected.
      */
-    private void onDisconnected(InCallServiceInfo disconnectedInfo) {
+    private void onDisconnected(InCallServiceInfo disconnectedInfo, UserHandle userHandle) {
         Log.i(this, "onDisconnected from %s", disconnectedInfo.getComponentName());
         if (disconnectedInfo.getType() == IN_CALL_SERVICE_TYPE_CAR_MODE_UI
                 || disconnectedInfo.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI
                 || disconnectedInfo.getType() == IN_CALL_SERVICE_TYPE_DEFAULT_DIALER_UI) {
             trackCallingUserInterfaceStopped(disconnectedInfo);
         }
-        mInCallServices.remove(disconnectedInfo);
+        if (mInCallServices.containsKey(userHandle)) {
+            mInCallServices.get(userHandle).remove(disconnectedInfo);
+        }
     }
 
     /**
@@ -1969,24 +2119,41 @@
      * @param call The {@link Call}.
      */
     private void updateCall(Call call) {
-        updateCall(call, false /* videoProviderChanged */, false);
+        updateCall(call, false /* videoProviderChanged */, false, null);
     }
 
     /**
      * Informs all {@link InCallService} instances of the updated call information.
      *
-     * @param call The {@link Call}.
+     * @param call                 The {@link Call}.
      * @param videoProviderChanged {@code true} if the video provider changed, {@code false}
-     *      otherwise.
-     * @param rttInfoChanged {@code true} if any information about the RTT session changed,
-     * {@code false} otherwise.
+     *                             otherwise.
+     * @param rttInfoChanged       {@code true} if any information about the RTT session changed,
+     *                             {@code false} otherwise.
+     * @param exceptPackageName    When specified, this package name will not get a call update.
+     *                             Used ONLY from {@link Call#putConnectionServiceExtras(int, Bundle, String)} to
+     *                             ensure we can propagate extras changes between InCallServices but
+     *                             not inform the requestor of their own change.
      */
-    private void updateCall(Call call, boolean videoProviderChanged, boolean rttInfoChanged) {
-        if (!mInCallServices.isEmpty()) {
+    private void updateCall(Call call, boolean videoProviderChanged, boolean rttInfoChanged,
+            String exceptPackageName) {
+        UserHandle userFromCall = getUserFromCall(call);
+        if (mInCallServices.containsKey(userFromCall)) {
             Log.i(this, "Sending updateCall %s", call);
             List<ComponentName> componentsUpdated = new ArrayList<>();
-            for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
+            for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.
+                    get(userFromCall).entrySet()) {
                 InCallServiceInfo info = entry.getKey();
+                ComponentName componentName = info.getComponentName();
+
+                // If specified, skip ICS if it matches the package name.  Used for cases where on
+                // ICS makes an update to extras and we want to skip updating the same ICS with the
+                // change that it implemented.
+                if (exceptPackageName != null
+                        && componentName.getPackageName().equals(exceptPackageName)) {
+                    continue;
+                }
+
                 if (call.isExternalCall() && !info.isExternalCallsSupported()) {
                     continue;
                 }
@@ -2001,10 +2168,10 @@
                         videoProviderChanged /* includeVideoProvider */,
                         mCallsManager.getPhoneAccountRegistrar(),
                         info.isExternalCallsSupported(),
-                        rttInfoChanged && info.equals(mInCallServiceConnection.getInfo()),
+                        rttInfoChanged && info.equals(
+                                mInCallServiceConnections.get(userFromCall).getInfo()),
                         info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI ||
                         info.getType() == IN_CALL_SERVICE_TYPE_NON_UI);
-                ComponentName componentName = info.getComponentName();
                 IInCallService inCallService = entry.getValue();
                 componentsUpdated.add(componentName);
 
@@ -2041,8 +2208,11 @@
     /**
      * @return true if we are bound to the UI InCallService and it is connected.
      */
-    private boolean isBoundAndConnectedToServices() {
-        return mInCallServiceConnection != null && mInCallServiceConnection.isConnected();
+    private boolean isBoundAndConnectedToServices(UserHandle userHandle) {
+        if (!mInCallServiceConnections.containsKey(userHandle)) {
+            return false;
+        }
+        return mInCallServiceConnections.get(userHandle).isConnected();
     }
 
     /**
@@ -2061,15 +2231,17 @@
     public void dump(IndentingPrintWriter pw) {
         pw.println("mInCallServices (InCalls registered):");
         pw.increaseIndent();
-        for (InCallServiceInfo info : mInCallServices.keySet()) {
-            pw.println(info);
-        }
+        mInCallServices.values().forEach(inCallServices -> {
+            for (InCallServiceInfo info : inCallServices.keySet()) {
+                pw.println(info);
+            }
+        });
         pw.decreaseIndent();
 
         pw.println("ServiceConnections (InCalls bound):");
         pw.increaseIndent();
-        if (mInCallServiceConnection != null) {
-            mInCallServiceConnection.dump(pw);
+        for (InCallServiceConnection inCallServiceConnection : mInCallServiceConnections.values()) {
+            inCallServiceConnection.dump(pw);
         }
         pw.decreaseIndent();
 
@@ -2079,22 +2251,25 @@
     /**
      * @return The package name of the UI which is currently bound, or null if none.
      */
-    private ComponentName getConnectedUi() {
-        InCallServiceInfo connectedUi = mInCallServices.keySet().stream().filter(
-                i -> i.getType() == IN_CALL_SERVICE_TYPE_DEFAULT_DIALER_UI
-                        || i.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI)
-                .findAny()
-                .orElse(null);
-        if (connectedUi != null) {
-            return connectedUi.mComponentName;
+    private ComponentName getConnectedUi(UserHandle userHandle) {
+        if (mInCallServices.containsKey(userHandle)) {
+            InCallServiceInfo connectedUi = mInCallServices.get(
+                            userHandle).keySet().stream().filter(
+                            i -> i.getType() == IN_CALL_SERVICE_TYPE_DEFAULT_DIALER_UI
+                                    || i.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI)
+                    .findAny()
+                    .orElse(null);
+            if (connectedUi != null) {
+                return connectedUi.mComponentName;
+            }
         }
         return null;
     }
 
-    public boolean doesConnectedDialerSupportRinging() {
+    public boolean doesConnectedDialerSupportRinging(UserHandle userHandle) {
         String ringingPackage =  null;
 
-        ComponentName connectedPackage = getConnectedUi();
+        ComponentName connectedPackage = getConnectedUi(userHandle);
         if (connectedPackage != null) {
             ringingPackage = connectedPackage.getPackageName().trim();
             Log.d(this, "doesConnectedDialerSupportRinging: alreadyConnectedPackage=%s",
@@ -2104,7 +2279,7 @@
         if (TextUtils.isEmpty(ringingPackage)) {
             // The current in-call UI returned nothing, so lets use the default dialer.
             ringingPackage = mDefaultDialerCache.getRoleManagerAdapter().getDefaultDialerApp(
-                    mCallsManager.getCurrentUserHandle().getIdentifier());
+                    userHandle.getIdentifier());
             if (ringingPackage != null) {
                 Log.d(this, "doesConnectedDialerSupportRinging: notCurentlyConnectedPackage=%s",
                         ringingPackage);
@@ -2119,7 +2294,7 @@
             .setPackage(ringingPackage);
         List<ResolveInfo> entries = mContext.getPackageManager().queryIntentServicesAsUser(
                 intent, PackageManager.GET_META_DATA,
-                mCallsManager.getCurrentUserHandle().getIdentifier());
+                userHandle.getIdentifier());
         if (entries.isEmpty()) {
             Log.w(this, "doesConnectedDialerSupportRinging: couldn't find dialer's package info"
                     + " <sad trombone>");
@@ -2151,15 +2326,32 @@
         return childCalls;
     }
 
-    private ParcelableCall sanitizeParcelableCallForService(
+    @VisibleForTesting
+    public ParcelableCall sanitizeParcelableCallForService(
             InCallServiceInfo info, ParcelableCall parcelableCall) {
         ParcelableCall.ParcelableCallBuilder builder =
                 ParcelableCall.ParcelableCallBuilder.fromParcelableCall(parcelableCall);
-        // Check for contacts permission. If it's not there, remove the contactsDisplayName.
         PackageManager pm = mContext.getPackageManager();
+
+        // Check for contacts permission.
         if (pm.checkPermission(Manifest.permission.READ_CONTACTS,
                 info.getComponentName().getPackageName()) != PackageManager.PERMISSION_GRANTED) {
+            // contacts permission is not present...
+
+            // removing the contactsDisplayName
             builder.setContactDisplayName(null);
+            builder.setContactPhotoUri(null);
+
+            // removing the Call.EXTRA_IS_SUPPRESSED_BY_DO_NOT_DISTURB extra
+            if (parcelableCall.getExtras() != null) {
+                Bundle callBundle = parcelableCall.getExtras();
+                if (callBundle.containsKey(
+                        android.telecom.Call.EXTRA_IS_SUPPRESSED_BY_DO_NOT_DISTURB)) {
+                    Bundle newBundle = callBundle.deepCopy();
+                    newBundle.remove(android.telecom.Call.EXTRA_IS_SUPPRESSED_BY_DO_NOT_DISTURB);
+                    builder.setExtras(newBundle);
+                }
+            }
         }
 
         // TODO: move all the other service-specific sanitizations in here
@@ -2181,8 +2373,8 @@
         // Disabled InCallService should also be considered as a valid InCallService here so that
         // it can be added to the CarModeTracker, in case it will be enabled in future.
         InCallServiceInfo info =
-                getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_CAR_MODE_UI,
-                        false /* ignoreDisabled */);
+                getInCallServiceComponent(mCallsManager.getCurrentUserHandle(),
+                        packageName, IN_CALL_SERVICE_TYPE_CAR_MODE_UI, false /* ignoreDisabled */);
         return info != null && info.getType() == IN_CALL_SERVICE_TYPE_CAR_MODE_UI;
     }
 
@@ -2231,16 +2423,19 @@
     public void updateCarModeForConnections() {
         Log.i(this, "updateCarModeForConnections: car mode apps: %s",
                 mCarModeTracker.getCarModeApps().stream().collect(Collectors.joining(", ")));
-        if (mInCallServiceConnection != null) {
+
+        if (mInCallServiceConnections.containsKey(mCallsManager.getCurrentUserHandle())) {
+            CarSwappingInCallServiceConnection inCallServiceConnection = mInCallServiceConnections.
+                    get(mCallsManager.getCurrentUserHandle());
             if (shouldUseCarModeUI()) {
                 Log.i(this, "updateCarModeForConnections: potentially update car mode app.");
-                mInCallServiceConnection.changeCarModeApp(
-                        mCarModeTracker.getCurrentCarModePackage());
+                inCallServiceConnection.changeCarModeApp(mCarModeTracker.getCurrentCarModePackage(),
+                        mCallsManager.getCurrentUserHandle());
             } else {
-                if (mInCallServiceConnection.isCarMode()) {
+                if (inCallServiceConnection.isCarMode()) {
                     Log.i(this, "updateCarModeForConnections: car mode no longer "
                             + "applicable; disabling");
-                    mInCallServiceConnection.disableCarMode();
+                    inCallServiceConnection.disableCarMode();
                 }
             }
         }
@@ -2303,6 +2498,7 @@
                 && !isCarrierPrivilegedUsingMicDuringVoipCall();
         if (wasUsingMicrophone != mIsCallUsingMicrophone) {
             if (mIsCallUsingMicrophone) {
+                // Note, not checking return value, as this op call is merely for tracing use
                 mAppOpsManager.startOp(AppOpsManager.OP_PHONE_CALL_MICROPHONE, myUid(),
                         mContext.getOpPackageName(), false, null, null);
                 mSensorPrivacyManager.showSensorUseDialog(SensorPrivacyManager.Sensors.MICROPHONE);
@@ -2348,7 +2544,7 @@
                                                         == PermissionChecker.PERMISSION_GRANTED;
     }
 
-    private void sendCrashedInCallServiceNotification(String packageName) {
+    private void sendCrashedInCallServiceNotification(String packageName, UserHandle userHandle) {
         PackageManager packageManager = mContext.getPackageManager();
         CharSequence appName;
         String systemDialer = mDefaultDialerCache.getSystemDialerApplication();
@@ -2376,8 +2572,8 @@
                 .setStyle(new Notification.BigTextStyle()
                         .bigText(mContext.getText(
                                 R.string.notification_incallservice_not_responding_body)));
-        notificationManager.notify(NOTIFICATION_TAG, IN_CALL_SERVICE_NOTIFICATION_ID,
-                builder.build());
+        notificationManager.notifyAsUser(NOTIFICATION_TAG, IN_CALL_SERVICE_NOTIFICATION_ID,
+                builder.build(), userHandle);
     }
 
     private void updateCallTracking(Call call, InCallServiceInfo info, boolean isAdd) {
@@ -2386,4 +2582,20 @@
                 || type == IN_CALL_SERVICE_TYPE_DEFAULT_DIALER_UI;
         call.maybeOnInCallServiceTrackingChanged(isAdd, hasUi);
     }
+
+    private UserHandle getUserFromCall(Call call) {
+        // Call may never be specified, so we can fall back to using the CallManager current user.
+        if (call == null) {
+            return mCallsManager.getCurrentUserHandle();
+        } else {
+            UserHandle userFromCall = call.getUserHandleFromTargetPhoneAccount();
+            UserManager userManager = mContext.getSystemService(UserManager.class);
+            // Emergency call should never be blocked, so if the user associated with call is in
+            // quite mode, use the primary user for the emergency call.
+            if (call.isEmergencyCall() && userManager.isQuietModeEnabled(userFromCall)) {
+                return mCallsManager.getCurrentUserHandle();
+            }
+            return userFromCall;
+        }
+    }
 }
diff --git a/src/com/android/server/telecom/InCallTonePlayer.java b/src/com/android/server/telecom/InCallTonePlayer.java
index 0367ba0..3cc4aac 100644
--- a/src/com/android/server/telecom/InCallTonePlayer.java
+++ b/src/com/android/server/telecom/InCallTonePlayer.java
@@ -170,7 +170,7 @@
 
     private static final int RELATIVE_VOLUME_EMERGENCY = 100;
     private static final int RELATIVE_VOLUME_HIPRI = 80;
-    private static final int RELATIVE_VOLUME_LOPRI = 50;
+    private static final int RELATIVE_VOLUME_LOPRI = 30;
     private static final int RELATIVE_VOLUME_UNDEFINED = -1;
 
     // Buffer time (in msec) to add on to the tone timeout value. Needed mainly when the timeout
@@ -470,12 +470,6 @@
 
     @VisibleForTesting
     public boolean startTone() {
-        // Skip playing the end call tone if the volume is silenced.
-        if (mToneId == TONE_CALL_ENDED && !mAudioManagerAdapter.isVolumeOverZero()) {
-            Log.i(this, "startTone: skip end-call tone as device is silenced.");
-            return false;
-        }
-
         // Tone already done; don't allow re-used
         if (mState == STATE_STOPPED) {
             return false;
diff --git a/src/com/android/server/telecom/LogUtils.java b/src/com/android/server/telecom/LogUtils.java
index 12e780f..8b1a256 100644
--- a/src/com/android/server/telecom/LogUtils.java
+++ b/src/com/android/server/telecom/LogUtils.java
@@ -88,6 +88,7 @@
         public static final String CSW_REMOVE_CALL = "CSW.rC";
         public static final String CSW_SET_IS_CONFERENCED = "CSW.sIC";
         public static final String CSW_ADD_CONFERENCE_CALL = "CSW.aCC";
+        public static final String CSA_SET_STATE = "CSA.sSS";
     }
 
     public final static class Events {
@@ -104,10 +105,17 @@
         public static final String SET_RINGING = "SET_RINGING";
         public static final String SET_ANSWERED = "SET_ANSWERED";
         public static final String SET_DISCONNECTED = "SET_DISCONNECTED";
+        public static final String SKIP_CALL_LOG = "SKIP_CALL_LOG";
+        public static final String LOG_CALL = "LOG_CALL";
         public static final String SET_DISCONNECTING = "SET_DISCONNECTING";
         public static final String SET_SELECT_PHONE_ACCOUNT = "SET_SELECT_PHONE_ACCOUNT";
         public static final String SET_AUDIO_PROCESSING = "SET_AUDIO_PROCESSING";
         public static final String SET_SIMULATED_RINGING = "SET_SIMULATED_RINGING";
+        public static final String REQUEST_RTT = "REQUEST_RTT";
+        public static final String RESPOND_TO_RTT_REQUEST = "RESPOND_TO_RTT_REQUEST";
+        public static final String SET_RRT_MODE = "SET_RTT_MODE";
+        public static final String ON_RTT_FAILED = "ON_RTT_FAILED";
+        public static final String ON_RTT_REQUEST = "ON_RTT_REQUEST";
         public static final String REQUEST_HOLD = "REQUEST_HOLD";
         public static final String REQUEST_UNHOLD = "REQUEST_UNHOLD";
         public static final String REQUEST_DISCONNECT = "REQUEST_DISCONNECT";
@@ -168,6 +176,8 @@
         public static final String DIRECT_TO_VM_INITIATED = "DIRECT_TO_VM_INITIATED";
         public static final String DIRECT_TO_VM_FINISHED = "DIRECT_TO_VM_FINISHED";
         public static final String FILTERING_INITIATED = "FILTERING_INITIATED";
+        public static final String DND_PRE_CHECK_INITIATED = "DND_PRE_CHECK_INITIATED";
+        public static final String DND_PRE_CHECK_COMPLETED = "DND_PRE_CHECK_COMPLETED";
         public static final String FILTERING_COMPLETED = "FILTERING_COMPLETED";
         public static final String FILTERING_TIMED_OUT = "FILTERING_TIMED_OUT";
         public static final String REMOTELY_HELD = "REMOTELY_HELD";
@@ -209,6 +219,10 @@
                 "CALL_DIAGNOSTIC_SERVICE_TIMEOUT";
         public static final String VERSTAT_CHANGED = "VERSTAT_CHANGED";
         public static final String SET_VOIP_MODE = "SET_VOIP_MODE";
+        public static final String STATE_TIMEOUT = "STATE_TIMEOUT";
+        public static final String ICS_EXTRAS_CHANGED = "ICS_EXTRAS_CHANGED";
+        public static final String FLASH_NOTIFICATION_START = "FLASH_NOTIFICATION_START";
+        public static final String FLASH_NOTIFICATION_STOP = "FLASH_NOTIFICATION_STOP";
 
         public static class Timings {
             public static final String ACCEPT_TIMING = "accept";
@@ -222,6 +236,7 @@
             public static final String DIRECT_TO_VM_FINISHED_TIMING = "direct_to_vm_finished";
             public static final String BLOCK_CHECK_FINISHED_TIMING = "block_check_finished";
             public static final String FILTERING_COMPLETED_TIMING = "filtering_completed";
+            public static final String DND_PRE_CHECK_COMPLETED_TIMING = "dnd_pre_check_completed";
             public static final String FILTERING_TIMED_OUT_TIMING = "filtering_timed_out";
             public static final String START_CONNECTION_TO_REQUEST_DISCONNECT_TIMING =
                     "start_connection_to_request_disconnect";
@@ -243,6 +258,8 @@
                             BLOCK_CHECK_FINISHED_TIMING),
                     new TimedEventPair(FILTERING_INITIATED, FILTERING_COMPLETED,
                             FILTERING_COMPLETED_TIMING),
+                    new TimedEventPair(DND_PRE_CHECK_INITIATED, DND_PRE_CHECK_COMPLETED,
+                            DND_PRE_CHECK_COMPLETED_TIMING),
                     new TimedEventPair(FILTERING_INITIATED, FILTERING_TIMED_OUT,
                             FILTERING_TIMED_OUT_TIMING, 6000L),
                     new TimedEventPair(START_CONNECTION, REQUEST_DISCONNECT,
diff --git a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
index 4569950..41aa2fb 100644
--- a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
+++ b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
@@ -139,7 +139,7 @@
                         disconnectTimeout = getDisconnectTimeoutFromApp(
                                 getResultExtras(false), disconnectTimeout);
                         endEarly = true;
-                    } else if (isPotentialEmergencyNumber(resultNumber)) {
+                    } else if (isEmergencyNumber(resultNumber)) {
                         Log.w(this, "Cannot modify outgoing call to emergency number %s.",
                                 resultNumber);
                         disconnectTimeout = 0;
@@ -273,14 +273,14 @@
             return result;
         }
 
-        final boolean isPotentialEmergencyNumber = isPotentialEmergencyNumber(number);
-        Log.v(this, "isPotentialEmergencyNumber = %s", isPotentialEmergencyNumber);
+        final boolean isEmergencyNumber = isEmergencyNumber(number);
+        Log.v(this, "isEmergencyNumber = %s", isEmergencyNumber);
 
-        action = calculateCallIntentAction(intent, isPotentialEmergencyNumber);
+        action = calculateCallIntentAction(intent, isEmergencyNumber);
         intent.setAction(action);
 
         if (Intent.ACTION_CALL.equals(action)) {
-            if (isPotentialEmergencyNumber) {
+            if (isEmergencyNumber) {
                 if (!mIsDefaultOrSystemPhoneApp) {
                     Log.w(this, "Cannot call potential emergency number %s with CALL Intent %s "
                             + "unless caller is system or default dialer.", number, intent);
@@ -293,7 +293,7 @@
                 }
             }
         } else if (Intent.ACTION_CALL_EMERGENCY.equals(action)) {
-            if (!isPotentialEmergencyNumber) {
+            if (!isEmergencyNumber) {
                 Log.w(this, "Cannot call non-potential-emergency number %s with EMERGENCY_CALL "
                         + "Intent %s.", number, intent);
                 result.disconnectCause = DisconnectCause.OUTGOING_CANCELED;
@@ -372,9 +372,9 @@
              * broadcasting.
              */
             callRedirectionWithService = callRedirectionProcessor
-                    .canMakeCallRedirectionWithService();
+                    .canMakeCallRedirectionWithServiceAsUser(mCall.getInitiatingUser());
             if (callRedirectionWithService) {
-                callRedirectionProcessor.performCallRedirection();
+                callRedirectionProcessor.performCallRedirection(mCall.getInitiatingUser());
             }
         }
 
@@ -517,23 +517,18 @@
      * that only the CALL_PRIVILEGED and CALL_EMERGENCY intents are allowed to make emergency
      * calls.
      *
-     * To prevent malicious 3rd party apps from making emergency calls by passing in an
-     * "invalid" number like "9111234" (that isn't technically an emergency number but might
-     * still result in an emergency call with some networks), we use
-     * isPotentialLocalEmergencyNumber instead of isLocalEmergencyNumber.
-     *
      * @param number number to inspect in order to determine whether or not an emergency number
-     * is potentially being dialed
-     * @return True if the handle is potentially an emergency number.
+     * is being dialed
+     * @return True if the handle is an emergency number.
      */
-    private boolean isPotentialEmergencyNumber(String number) {
+    private boolean isEmergencyNumber(String number) {
         Log.v(this, "Checking restrictions for number : %s", Log.pii(number));
         if (number == null) return false;
         try {
-            return mContext.getSystemService(TelephonyManager.class).isPotentialEmergencyNumber(
+            return mContext.getSystemService(TelephonyManager.class).isEmergencyNumber(
                     number);
         } catch (Exception e) {
-            Log.e(this, e, "isPotentialEmergencyNumber: Telephony threw an exception.");
+            Log.e(this, e, "isEmergencyNumber: Telephony threw an exception.");
             return false;
         }
     }
@@ -543,17 +538,17 @@
      * the appropriate call intent action.
      *
      * @param intent Intent to evaluate
-     * @param isPotentialEmergencyNumber Whether or not the number is potentially an emergency
+     * @param isEmergencyNumber Whether or not the number is an emergency
      * number.
      * @return The appropriate action.
      */
-    private String calculateCallIntentAction(Intent intent, boolean isPotentialEmergencyNumber) {
+    private String calculateCallIntentAction(Intent intent, boolean isEmergencyNumber) {
         String action = intent.getAction();
 
         /* Change CALL_PRIVILEGED into CALL or CALL_EMERGENCY as needed. */
         if (Intent.ACTION_CALL_PRIVILEGED.equals(action)) {
-            if (isPotentialEmergencyNumber) {
-                Log.i(this, "ACTION_CALL_PRIVILEGED is used while the number is a potential"
+            if (isEmergencyNumber) {
+                Log.i(this, "ACTION_CALL_PRIVILEGED is used while the number is a"
                         + " emergency number. Using ACTION_CALL_EMERGENCY as an action instead.");
                 action = Intent.ACTION_CALL_EMERGENCY;
             } else {
diff --git a/src/com/android/server/telecom/ParcelableCallUtils.java b/src/com/android/server/telecom/ParcelableCallUtils.java
index 0becaef..673b99a 100644
--- a/src/com/android/server/telecom/ParcelableCallUtils.java
+++ b/src/com/android/server/telecom/ParcelableCallUtils.java
@@ -196,6 +196,8 @@
         String callerDisplayName = call.getCallerDisplayNamePresentation() ==
                 TelecomManager.PRESENTATION_ALLOWED ?  call.getCallerDisplayName() : null;
 
+        Uri contactPhotoUri = call.getContactPhotoUri();
+
         List<Call> conferenceableCalls = call.getConferenceableCalls();
         List<String> conferenceableCallIds = new ArrayList<String>(conferenceableCalls.size());
         for (Call otherCall : conferenceableCalls) {
@@ -255,6 +257,7 @@
                 .setCallerNumberVerificationStatus(call.getCallerNumberVerificationStatus())
                 .setContactDisplayName(call.getName())
                 .setActiveChildCallId(activeChildCallId)
+                .setContactPhotoUri(contactPhotoUri)
                 .createParcelableCall();
     }
 
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index 576a289..f5a3450 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -32,6 +32,7 @@
 import android.graphics.BitmapFactory;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
+import android.os.Binder;
 import android.os.Bundle;
 import android.os.AsyncTask;
 import android.os.PersistableBundle;
@@ -41,7 +42,6 @@
 import android.provider.Settings;
 import android.telecom.CallAudioState;
 import android.telecom.ConnectionService;
-import android.telecom.DefaultDialerManager;
 import android.telecom.Log;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
@@ -58,15 +58,14 @@
 
 // TODO: Needed for move to system service: import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.XmlUtils;
+import com.android.modules.utils.ModifiedUtf8;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
 
-import java.io.BufferedInputStream;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
@@ -85,12 +84,9 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Optional;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.stream.Collector;
 import java.util.stream.Collectors;
-import java.util.stream.Stream;
 
 /**
  * Handles writing and reading PhoneAccountHandle registration entries. This is a simple verbatim
@@ -157,9 +153,14 @@
     };
 
     public static final String FILE_NAME = "phone-account-registrar-state.xml";
+    public static final String ICON_ERROR_MSG =
+            "Icon cannot be written to memory. Try compressing or downsizing";
     @VisibleForTesting
     public static final int EXPECTED_STATE_VERSION = 9;
     public static final int MAX_PHONE_ACCOUNT_REGISTRATIONS = 10;
+    public static final int MAX_PHONE_ACCOUNT_EXTRAS_KEY_PAIR_LIMIT = 100;
+    public static final int MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT = 256;
+    public static final int MAX_SCHEMES_PER_ACCOUNT = 10;
 
     /** Keep in sync with the same in SipSettings.java */
     private static final String SIP_SHARED_PREFERENCES = "SIP_PREFERENCES";
@@ -248,7 +249,7 @@
         }
 
         List<PhoneAccountHandle> outgoing = getCallCapablePhoneAccounts(uriScheme, false,
-                userHandle);
+                userHandle, false);
         switch (outgoing.size()) {
             case 0:
                 // There are no accounts, so there can be no default
@@ -323,7 +324,7 @@
         }
         // Get the PhoneAccount with the same group Id (and same ComponentName) that is not the
         // newAccount that was just added
-        List<PhoneAccount> accounts = getAllPhoneAccounts(userHandle).stream()
+        List<PhoneAccount> accounts = getAllPhoneAccounts(userHandle, false).stream()
                 .filter(account -> groupId.equals(account.getGroupId()) &&
                         !account.getAccountHandle().equals(excludePhoneAccountHandle) &&
                         Objects.equals(account.getAccountHandle().getComponentName(),
@@ -469,7 +470,7 @@
             // loop through and look for any connection manager in the same package.
             List<PhoneAccountHandle> allSimCallManagers = getPhoneAccountHandles(
                     PhoneAccount.CAPABILITY_CONNECTION_MANAGER, null, null,
-                    true /* includeDisabledAccounts */, userHandle);
+                    true /* includeDisabledAccounts */, userHandle, false);
             for (PhoneAccountHandle accountHandle : allSimCallManagers) {
                 ComponentName component = accountHandle.getComponentName();
 
@@ -725,16 +726,13 @@
      *
      * @return The list of {@link PhoneAccountHandle}s.
      */
-    public List<PhoneAccountHandle> getAllPhoneAccountHandles(UserHandle userHandle) {
-        return getPhoneAccountHandles(0, null, null, false, userHandle);
+    public List<PhoneAccountHandle> getAllPhoneAccountHandles(UserHandle userHandle,
+            boolean crossUserAccess) {
+        return getPhoneAccountHandles(0, null, null, false, userHandle, crossUserAccess);
     }
 
-    public List<PhoneAccount> getAllPhoneAccounts(UserHandle userHandle) {
-        return getPhoneAccounts(0, null, null, false, userHandle);
-    }
-
-    public List<PhoneAccount> getAllPhoneAccountsOfCurrentUser() {
-        return getAllPhoneAccounts(mCurrentUserHandle);
+    public List<PhoneAccount> getAllPhoneAccounts(UserHandle userHandle, boolean crossUserAccess) {
+        return getPhoneAccounts(0, null, null, false, mCurrentUserHandle, crossUserAccess);
     }
 
     /**
@@ -748,9 +746,11 @@
      * @return The phone account handles.
      */
     public List<PhoneAccountHandle> getCallCapablePhoneAccounts(
-            String uriScheme, boolean includeDisabledAccounts, UserHandle userHandle) {
+            String uriScheme, boolean includeDisabledAccounts,
+            UserHandle userHandle, boolean crossUserAccess) {
         return getCallCapablePhoneAccounts(uriScheme, includeDisabledAccounts, userHandle,
-                0 /* capabilities */, PhoneAccount.CAPABILITY_EMERGENCY_CALLS_ONLY);
+                0 /* capabilities */, PhoneAccount.CAPABILITY_EMERGENCY_CALLS_ONLY,
+                crossUserAccess);
     }
 
     /**
@@ -767,11 +767,11 @@
      */
     public List<PhoneAccountHandle> getCallCapablePhoneAccounts(
             String uriScheme, boolean includeDisabledAccounts, UserHandle userHandle,
-            int capabilities, int excludedCapabilities) {
+            int capabilities, int excludedCapabilities, boolean crossUserAccess) {
         return getPhoneAccountHandles(
                 PhoneAccount.CAPABILITY_CALL_PROVIDER | capabilities,
                 excludedCapabilities /*excludedCapabilities*/,
-                uriScheme, null, includeDisabledAccounts, userHandle);
+                uriScheme, null, includeDisabledAccounts, userHandle, crossUserAccess);
     }
 
     /**
@@ -789,12 +789,7 @@
                 PhoneAccount.CAPABILITY_SELF_MANAGED,
                 PhoneAccount.CAPABILITY_EMERGENCY_CALLS_ONLY /* excludedCapabilities */,
                 null /* uriScheme */, null /* packageName */, false /* includeDisabledAccounts */,
-                userHandle);
-    }
-
-    public List<PhoneAccountHandle> getCallCapablePhoneAccountsOfCurrentUser(
-            String uriScheme, boolean includeDisabledAccounts) {
-        return getCallCapablePhoneAccounts(uriScheme, includeDisabledAccounts, mCurrentUserHandle);
+                userHandle, false);
     }
 
     /**
@@ -803,7 +798,7 @@
     public List<PhoneAccountHandle> getSimPhoneAccounts(UserHandle userHandle) {
         return getPhoneAccountHandles(
                 PhoneAccount.CAPABILITY_CALL_PROVIDER | PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION,
-                null, null, false, userHandle);
+                null, null, false, userHandle, false);
     }
 
     public List<PhoneAccountHandle> getSimPhoneAccountsOfCurrentUser() {
@@ -818,7 +813,17 @@
          */
     public List<PhoneAccountHandle> getPhoneAccountsForPackage(String packageName,
             UserHandle userHandle) {
-        return getPhoneAccountHandles(0, null, packageName, false, userHandle);
+        return getPhoneAccountHandles(0, null, packageName, false, userHandle, false);
+    }
+
+
+    /**
+     * includes disabled, includes crossUserAccess
+     */
+    public List<PhoneAccountHandle> getAllPhoneAccountHandlesForPackage(UserHandle userHandle,
+            String packageName) {
+        return getPhoneAccountHandles(0, null, packageName, true /* includeDisabled */, userHandle,
+                true /* crossUserAccess */);
     }
 
     /**
@@ -859,34 +864,190 @@
      * Performs checks before calling addOrReplacePhoneAccount(PhoneAccount)
      *
      * @param account The {@code PhoneAccount} to add or replace.
-     * @throws SecurityException if package does not have BIND_TELECOM_CONNECTION_SERVICE permission
+     * @throws SecurityException        if package does not have BIND_TELECOM_CONNECTION_SERVICE
+     *                                  permission
      * @throws IllegalArgumentException if MAX_PHONE_ACCOUNT_REGISTRATIONS are reached
+     * @throws IllegalArgumentException if MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT is reached
+     * @throws IllegalArgumentException if writing the Icon to memory will cause an Exception
      */
     public void registerPhoneAccount(PhoneAccount account) {
         // Enforce the requirement that a connection service for a phone account has the correct
         // permission.
-        if (!phoneAccountRequiresBindPermission(account.getAccountHandle())) {
+        if (!hasTransactionalCallCapabilities(account) &&
+                !phoneAccountRequiresBindPermission(account.getAccountHandle())) {
             Log.w(this,
                     "Phone account %s does not have BIND_TELECOM_CONNECTION_SERVICE permission.",
                     account.getAccountHandle());
-            throw new SecurityException("PhoneAccount connection service requires "
-                    + "BIND_TELECOM_CONNECTION_SERVICE permission.");
+            throw new SecurityException("Registering a PhoneAccount requires either: "
+                    + "(1) The Service definition requires that the ConnectionService is guarded"
+                    + " with the BIND_TELECOM_CONNECTION_SERVICE, which can be defined using the"
+                    + " android:permission tag as part of the Service definition. "
+                    + "(2) The PhoneAccount capability called"
+                    + " CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS.");
         }
-        //Enforce an upper bound on the number of PhoneAccount's a package can register.
-        // Most apps should only require 1-2.
-        if (getPhoneAccountsForPackage(
-                account.getAccountHandle().getComponentName().getPackageName(),
-                account.getAccountHandle().getUserHandle()).size()
+        enforceCharacterLimit(account);
+        enforceIconSizeLimit(account);
+        enforceMaxPhoneAccountLimit(account);
+        addOrReplacePhoneAccount(account);
+    }
+
+    /**
+     * Enforce an upper bound on the number of PhoneAccount's a package can register.
+     * Most apps should only require 1-2.  * Include disabled accounts.
+     *
+     * @param account to enforce check on
+     * @throws IllegalArgumentException if MAX_PHONE_ACCOUNT_REGISTRATIONS are reached
+     */
+    private void enforceMaxPhoneAccountLimit(@NonNull PhoneAccount account) {
+        final PhoneAccountHandle accountHandle = account.getAccountHandle();
+        final UserHandle user = accountHandle.getUserHandle();
+        final ComponentName componentName = accountHandle.getComponentName();
+
+        if (getPhoneAccountHandles(0, null, componentName.getPackageName(),
+                true /* includeDisabled */, user, false /* crossUserAccess */).size()
                 >= MAX_PHONE_ACCOUNT_REGISTRATIONS) {
-            Log.w(this, "Phone account %s reached max registration limit for package",
-                    account.getAccountHandle());
+            EventLog.writeEvent(0x534e4554, "259064622", Binder.getCallingUid(),
+                    "enforceMaxPhoneAccountLimit");
             throw new IllegalArgumentException(
                     "Error, cannot register phone account " + account.getAccountHandle()
                             + " because the limit, " + MAX_PHONE_ACCOUNT_REGISTRATIONS
                             + ", has been reached");
         }
+    }
 
-        addOrReplacePhoneAccount(account);
+    /**
+     * determine if there will be an issue writing the icon to memory
+     *
+     * @param account to enforce check on
+     * @throws IllegalArgumentException if writing the Icon to memory will cause an Exception
+     */
+    @VisibleForTesting
+    public void enforceIconSizeLimit(PhoneAccount account) {
+        if (account.getIcon() == null) {
+            return;
+        }
+        String text = "";
+        // convert the icon into a Base64 String
+        try {
+            text = XmlSerialization.writeIconToBase64String(account.getIcon());
+        } catch (IOException e) {
+            EventLog.writeEvent(0x534e4554, "259064622", Binder.getCallingUid(),
+                    "enforceIconSizeLimit");
+            throw new IllegalArgumentException(ICON_ERROR_MSG);
+        }
+        // enforce the max bytes check in com.android.modules.utils.FastDataOutput#writeUTF(string)
+        try {
+            final int len = (int) ModifiedUtf8.countBytes(text, false);
+            if (len > 65_535 /* MAX_UNSIGNED_SHORT */) {
+                EventLog.writeEvent(0x534e4554, "259064622", Binder.getCallingUid(),
+                        "enforceIconSizeLimit");
+                throw new IllegalArgumentException(ICON_ERROR_MSG);
+            }
+        } catch (IOException e) {
+            EventLog.writeEvent(0x534e4554, "259064622", Binder.getCallingUid(),
+                    "enforceIconSizeLimit");
+            throw new IllegalArgumentException(ICON_ERROR_MSG);
+        }
+    }
+
+    /**
+     * All {@link PhoneAccount} and{@link PhoneAccountHandle} String and Char-Sequence fields
+     * should be restricted to character limit of MAX_PHONE_ACCOUNT_CHAR_LIMIT to prevent exceptions
+     * when writing large character streams to XML-Serializer.
+     *
+     * @param account to enforce character limit checks on
+     * @throws IllegalArgumentException if MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT reached
+     */
+    public void enforceCharacterLimit(PhoneAccount account) {
+        if (account == null) {
+            return;
+        }
+        PhoneAccountHandle handle = account.getAccountHandle();
+
+        String[] fields =
+                {"Package Name", "Class Name", "PhoneAccountHandle Id", "Label", "ShortDescription",
+                        "GroupId", "Address", "SubscriptionAddress"};
+        CharSequence[] args = {handle.getComponentName().getPackageName(),
+                handle.getComponentName().getClassName(), handle.getId(), account.getLabel(),
+                account.getShortDescription(), account.getGroupId(),
+                (account.getAddress() != null ? account.getAddress().toString() : ""),
+                (account.getSubscriptionAddress() != null ?
+                        account.getSubscriptionAddress().toString() : "")};
+
+        for (int i = 0; i < fields.length; i++) {
+            if (args[i] != null && args[i].length() > MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT) {
+                EventLog.writeEvent(0x534e4554, "259064622", Binder.getCallingUid(),
+                        "enforceCharacterLimit");
+                throw new IllegalArgumentException("The PhoneAccount or PhoneAccountHandle ["
+                        + fields[i] + "] field has an invalid character count. PhoneAccount and "
+                        + "PhoneAccountHandle String and Char-Sequence fields are limited to "
+                        + MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT + " characters.");
+            }
+        }
+
+        // Enforce limits on the URI Schemes provided
+        enforceLimitsOnSchemes(account);
+
+        // Enforce limit on the PhoneAccount#mExtras
+        Bundle extras = account.getExtras();
+        if (extras != null) {
+            if (extras.keySet().size() > MAX_PHONE_ACCOUNT_EXTRAS_KEY_PAIR_LIMIT) {
+                EventLog.writeEvent(0x534e4554, "259064622", Binder.getCallingUid(),
+                        "enforceCharacterLimit");
+                throw new IllegalArgumentException("The PhoneAccount#mExtras is limited to " +
+                        MAX_PHONE_ACCOUNT_EXTRAS_KEY_PAIR_LIMIT + " (key,value) pairs.");
+            }
+
+            for (String key : extras.keySet()) {
+                Object value = extras.get(key);
+
+                if ((key != null && key.length() > MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT) ||
+                        (value instanceof String &&
+                                ((String) value).length() > MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT)) {
+                    EventLog.writeEvent(0x534e4554, "259064622", Binder.getCallingUid(),
+                            "enforceCharacterLimit");
+                    throw new IllegalArgumentException("The PhoneAccount#mExtras contains a String"
+                            + " key or value that has an invalid character count. PhoneAccount and "
+                            + "PhoneAccountHandle String and Char-Sequence fields are limited to "
+                            + MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT + " characters.");
+                }
+            }
+        }
+    }
+
+    /**
+     * Enforce a character limit on all PA and PAH string or char-sequence fields.
+     *
+     * @param account to enforce check on
+     * @throws IllegalArgumentException if MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT reached
+     */
+    @VisibleForTesting
+    public void enforceLimitsOnSchemes(@NonNull PhoneAccount account) {
+        List<String> schemes = account.getSupportedUriSchemes();
+
+        if (schemes == null) {
+            return;
+        }
+
+        if (schemes.size() > MAX_SCHEMES_PER_ACCOUNT) {
+            EventLog.writeEvent(0x534e4554, "259064622", Binder.getCallingUid(),
+                    "enforceLimitsOnSchemes");
+            throw new IllegalArgumentException(
+                    "Error, cannot register phone account " + account.getAccountHandle()
+                            + " because the URI scheme limit of "
+                            + MAX_SCHEMES_PER_ACCOUNT + " has been reached");
+        }
+
+        for (String scheme : schemes) {
+            if (scheme.length() > MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT) {
+                EventLog.writeEvent(0x534e4554, "259064622", Binder.getCallingUid(),
+                        "enforceLimitsOnSchemes");
+                throw new IllegalArgumentException(
+                        "Error, cannot register phone account " + account.getAccountHandle()
+                                + " because the max scheme limit of "
+                                + MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT + " has been reached");
+            }
+        }
     }
 
     /**
@@ -904,6 +1065,15 @@
         boolean isEnabled = false;
         boolean isNewAccount;
 
+        // add self-managed capability for transactional accounts that are missing it
+        if (hasTransactionalCallCapabilities(account) &&
+                !account.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)) {
+            account = account.toBuilder()
+                    .setCapabilities(account.getCapabilities()
+                            | PhoneAccount.CAPABILITY_SELF_MANAGED)
+                    .build();
+        }
+
         PhoneAccount oldAccount = getPhoneAccountUnchecked(account.getAccountHandle());
         if (oldAccount != null) {
             enforceSelfManagedAccountUnmodified(account, oldAccount);
@@ -1184,7 +1354,11 @@
             // This may be null if there are no active SIMs but the device is still camped for
             // emergency calls and registered a SIM_SUBSCRIPTION for that purpose.
             TelephonyManager simTm = mTelephonyManager.createForPhoneAccountHandle(simHandle);
-            if (simTm == null) continue;
+            if (simTm == null) {
+                Log.i(this, "maybeNotifyTelephonyForVoiceServiceState: "
+                        + "simTm is null.");
+                continue;
+            }
             simTm.setVoiceServiceStateOverride(hasService);
         }
     }
@@ -1202,6 +1376,7 @@
             Log.w(this, "phoneAccount %s not found", phoneAccountHandle.getComponentName());
             return false;
         }
+
         for (ResolveInfo resolveInfo : resolveInfos) {
             ServiceInfo serviceInfo = resolveInfo.serviceInfo;
             if (serviceInfo == null) {
@@ -1220,6 +1395,15 @@
         return true;
     }
 
+    @VisibleForTesting
+    public boolean hasTransactionalCallCapabilities(PhoneAccount phoneAccount) {
+        if (phoneAccount == null) {
+            return false;
+        }
+        return phoneAccount.hasCapabilities(
+                PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS);
+    }
+
     //
     // Methods for retrieving PhoneAccounts and PhoneAccountHandles
     //
@@ -1266,9 +1450,10 @@
             String uriScheme,
             String packageName,
             boolean includeDisabledAccounts,
-            UserHandle userHandle) {
+            UserHandle userHandle,
+            boolean crossUserAccess) {
         return getPhoneAccountHandles(capabilities, 0 /*excludedCapabilities*/, uriScheme,
-                packageName, includeDisabledAccounts, userHandle);
+                packageName, includeDisabledAccounts, userHandle, crossUserAccess);
     }
 
     /**
@@ -1281,12 +1466,13 @@
             String uriScheme,
             String packageName,
             boolean includeDisabledAccounts,
-            UserHandle userHandle) {
+            UserHandle userHandle,
+            boolean crossUserAccess) {
         List<PhoneAccountHandle> handles = new ArrayList<>();
 
         for (PhoneAccount account : getPhoneAccounts(
                 capabilities, excludedCapabilities, uriScheme, packageName,
-                includeDisabledAccounts, userHandle)) {
+                includeDisabledAccounts, userHandle, crossUserAccess)) {
             handles.add(account.getAccountHandle());
         }
         return handles;
@@ -1297,9 +1483,10 @@
             String uriScheme,
             String packageName,
             boolean includeDisabledAccounts,
-            UserHandle userHandle) {
+            UserHandle userHandle,
+            boolean crossUserAccess) {
         return getPhoneAccounts(capabilities, 0 /*excludedCapabilities*/, uriScheme, packageName,
-                includeDisabledAccounts, userHandle);
+                includeDisabledAccounts, userHandle, crossUserAccess);
     }
 
     /**
@@ -1319,7 +1506,8 @@
             String uriScheme,
             String packageName,
             boolean includeDisabledAccounts,
-            UserHandle userHandle) {
+            UserHandle userHandle,
+            boolean crossUserAccess) {
         List<PhoneAccount> accounts = new ArrayList<>(mState.accounts.size());
         for (PhoneAccount m : mState.accounts) {
             if (!(m.isEnabled() || includeDisabledAccounts)) {
@@ -1342,7 +1530,10 @@
             }
             PhoneAccountHandle handle = m.getAccountHandle();
 
-            if (resolveComponent(handle).isEmpty()) {
+            // PhoneAccounts with CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS do not require a
+            // ConnectionService and will fail [resolveComponent(PhoneAccountHandle)]. Bypass
+            // the [resolveComponent(PhoneAccountHandle)] for transactional accounts.
+            if (!hasTransactionalCallCapabilities(m) && resolveComponent(handle).isEmpty()) {
                 // This component cannot be resolved anymore; skip this one.
                 continue;
             }
@@ -1351,7 +1542,7 @@
                 // Not the right package name; skip this one.
                 continue;
             }
-            if (!isVisibleForUser(m, userHandle, false)) {
+            if (!crossUserAccess && !isVisibleForUser(m, userHandle, false)) {
                 // Account is not visible for the current user; skip this one.
                 continue;
             }
@@ -1778,17 +1969,20 @@
         protected void writeIconIfNonNull(String tagName, Icon value, XmlSerializer serializer)
                 throws IOException {
             if (value != null) {
-                ByteArrayOutputStream stream = new ByteArrayOutputStream();
-                value.writeToStream(stream);
-                byte[] iconByteArray = stream.toByteArray();
-                String text = Base64.encodeToString(iconByteArray, 0, iconByteArray.length, 0);
-
+                String text = writeIconToBase64String(value);
                 serializer.startTag(null, tagName);
                 serializer.text(text);
                 serializer.endTag(null, tagName);
             }
         }
 
+        public static String writeIconToBase64String(Icon icon) throws IOException {
+            ByteArrayOutputStream stream = new ByteArrayOutputStream();
+            icon.writeToStream(stream);
+            byte[] iconByteArray = stream.toByteArray();
+            return Base64.encodeToString(iconByteArray, 0, iconByteArray.length, 0);
+        }
+
         protected void writeLong(String tagName, long value, XmlSerializer serializer)
                 throws IOException {
             serializer.startTag(null, tagName);
diff --git a/src/com/android/server/telecom/RespondViaSmsSettings.java b/src/com/android/server/telecom/RespondViaSmsSettings.java
index 661038b..d038a6e 100755
--- a/src/com/android/server/telecom/RespondViaSmsSettings.java
+++ b/src/com/android/server/telecom/RespondViaSmsSettings.java
@@ -89,6 +89,8 @@
         if (actionBar != null) {
             // android.R.id.home will be triggered in onOptionsItemSelected()
             actionBar.setDisplayHomeAsUpEnabled(true);
+            // set the talkback voice prompt to "Back" instead of "Navigate Up"
+            actionBar.setHomeActionContentDescription(R.string.back);
         }
     }
 
diff --git a/src/com/android/server/telecom/Ringer.java b/src/com/android/server/telecom/Ringer.java
index c859fde..b6aa4cc 100644
--- a/src/com/android/server/telecom/Ringer.java
+++ b/src/com/android/server/telecom/Ringer.java
@@ -21,6 +21,7 @@
 import static android.provider.CallLog.Calls.USER_MISSED_NO_VIBRATE;
 import static android.provider.Settings.Global.ZEN_MODE_OFF;
 
+import android.annotation.NonNull;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.Person;
@@ -32,11 +33,14 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.UserHandle;
+import android.os.UserManager;
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.telecom.Log;
 import android.telecom.TelecomManager;
+import android.view.accessibility.AccessibilityManager;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.telecom.LogUtils.EventTimer;
@@ -47,12 +51,24 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
+import java.util.function.BiConsumer;
+import java.util.function.Supplier;
 
 /**
  * Controls the ringtone player.
  */
 @VisibleForTesting
 public class Ringer {
+    public interface AccessibilityManagerAdapter {
+        boolean startFlashNotificationSequence(@NonNull Context context,
+                @AccessibilityManager.FlashNotificationReason int reason);
+        boolean stopFlashNotificationSequence(@NonNull Context context);
+    }
+    /**
+     * Flag only for local debugging. Do not submit enabled.
+     */
+    private static final boolean DEBUG_RINGER = false;
+
     public static class VibrationEffectProxy {
         public VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
             return VibrationEffect.createWaveform(timings, amplitudes, repeat);
@@ -152,11 +168,11 @@
      */
     private CompletableFuture<Void> mBlockOnRingingFuture = null;
 
-    private CompletableFuture<Void> mVibrateFuture = CompletableFuture.completedFuture(null);
-
     private InCallTonePlayer mCallWaitingPlayer;
     private RingtoneFactory mRingtoneFactory;
     private AudioManager mAudioManager;
+    private NotificationManager mNotificationManager;
+    private AccessibilityManagerAdapter mAccessibilityManagerAdapter;
 
     /**
      * Call objects that are ringing, vibrating or call-waiting. These are used only for logging
@@ -189,7 +205,9 @@
             RingtoneFactory ringtoneFactory,
             Vibrator vibrator,
             VibrationEffectProxy vibrationEffectProxy,
-            InCallController inCallController) {
+            InCallController inCallController,
+            NotificationManager notificationManager,
+            AccessibilityManagerAdapter accessibilityManagerAdapter) {
 
         mLock = new Object();
         mSystemSettingsUtil = systemSettingsUtil;
@@ -202,6 +220,8 @@
         mRingtoneFactory = ringtoneFactory;
         mInCallController = inCallController;
         mVibrationEffectProxy = vibrationEffectProxy;
+        mNotificationManager = notificationManager;
+        mAccessibilityManagerAdapter = accessibilityManagerAdapter;
 
         if (mContext.getResources().getBoolean(R.bool.use_simple_vibration_pattern)) {
             mDefaultVibrationEffect = mVibrationEffectProxy.createWaveform(SIMPLE_VIBRATION_PATTERN,
@@ -213,6 +233,8 @@
 
         mIsHapticPlaybackSupportedByDevice =
                 mSystemSettingsUtil.isHapticPlaybackSupported(mContext);
+
+        mAudioManager = mContext.getSystemService(AudioManager.class);
     }
 
     @VisibleForTesting
@@ -220,194 +242,274 @@
         mBlockOnRingingFuture = future;
     }
 
-    public boolean startRinging(Call foregroundCall, boolean isHfpDeviceAttached) {
-        if (foregroundCall == null) {
-            Log.wtf(this, "startRinging called with null foreground call.");
-            return false;
-        }
-
-        if (foregroundCall.getState() != CallState.RINGING
-                && foregroundCall.getState() != CallState.SIMULATED_RINGING) {
-            // Its possible for bluetooth to connect JUST as a call goes active, which would mean
-            // the call would start ringing again.
-            Log.i(this, "startRinging called for non-ringing foreground callid=%s",
-                    foregroundCall.getId());
-            return false;
-        }
-
-        // Use completable future to establish a timeout, not intent to make these work outside the
-        // main thread asynchronously
-        // TODO: moving these RingerAttributes calculation out of Telecom lock to avoid blocking.
-        CompletableFuture<RingerAttributes> ringerAttributesFuture = CompletableFuture
-                .supplyAsync(() -> getRingerAttributes(foregroundCall, isHfpDeviceAttached),
-                        new LoggedHandlerExecutor(getHandler(), "R.sR", null));
-
-        RingerAttributes attributes = null;
-        try {
-            mAttributesLatch = new CountDownLatch(1);
-            attributes = ringerAttributesFuture.get(
-                    RINGER_ATTRIBUTES_TIMEOUT, TimeUnit.MILLISECONDS);
-        } catch (ExecutionException | InterruptedException | TimeoutException e) {
-            // Keep attributs as null
-            Log.i(this, "getAttributes error: " + e);
-        }
-
-        if (attributes == null) {
-            Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "RingerAttributes error");
-            return false;
-        }
-
-        if (attributes.isEndEarly()) {
-            if (attributes.letDialerHandleRinging()) {
-                Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Dialer handles");
-            }
-            if (attributes.isSilentRingingRequested()) {
-                Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Silent ringing "
-                        + "requested");
-            }
-            if (mBlockOnRingingFuture != null) {
-                mBlockOnRingingFuture.complete(null);
-            }
-            return attributes.shouldAcquireAudioFocus();
-        }
-
-        stopCallWaiting();
-
-        VibrationEffect effect;
-        CompletableFuture<Boolean> hapticsFuture = null;
-        // Determine if the settings and DND mode indicate that the vibrator can be used right now.
-        boolean isVibratorEnabled = isVibratorEnabled(mContext, attributes.shouldRingForContact());
-        boolean shouldApplyRampingRinger =
-                isVibratorEnabled && mSystemSettingsUtil.isRampingRingerEnabled(mContext);
-        if (attributes.isRingerAudible()) {
-            mRingingCall = foregroundCall;
-            Log.addEvent(foregroundCall, LogUtils.Events.START_RINGER);
-            // Because we wait until a contact info query to complete before processing a
-            // call (for the purposes of direct-to-voicemail), the information about custom
-            // ringtones should be available by the time this code executes. We can safely
-            // request the custom ringtone from the call and expect it to be current.
-            if (shouldApplyRampingRinger) {
-                Log.i(this, "start ramping ringer.");
-                if (mSystemSettingsUtil.isAudioCoupledVibrationForRampingRingerEnabled()) {
-                    effect = getVibrationEffectForCall(mRingtoneFactory, foregroundCall);
-                } else {
-                    effect = mDefaultVibrationEffect;
-                }
-                if (mVolumeShaperConfig == null) {
-                    float silencePoint = (float) (RAMPING_RINGER_VIBRATION_DURATION)
-                            / (float) (RAMPING_RINGER_VIBRATION_DURATION + RAMPING_RINGER_DURATION);
-                    mVolumeShaperConfig = new VolumeShaper.Configuration.Builder()
-                            .setDuration(
-                                    RAMPING_RINGER_VIBRATION_DURATION + RAMPING_RINGER_DURATION)
-                            .setCurve(new float[]{0.f, silencePoint + EPSILON /*keep monotonicity*/,
-                                    1.f}, new float[]{0.f, 0.f, 1.f})
-                            .setInterpolatorType(
-                                    VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR)
-                            .build();
-                }
-                hapticsFuture = mRingtonePlayer.play(mRingtoneFactory, foregroundCall,
-                        mVolumeShaperConfig, attributes.isRingerAudible(), isVibratorEnabled);
-            } else {
-                // Ramping ringtone is not enabled.
-                hapticsFuture = mRingtonePlayer.play(mRingtoneFactory, foregroundCall, null,
-                        attributes.isRingerAudible(), isVibratorEnabled);
-                effect = getVibrationEffectForCall(mRingtoneFactory, foregroundCall);
-            }
-        } else {
-            Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Inaudible: "
-                    + attributes.getInaudibleReason());
-            if (isVibratorEnabled && mIsHapticPlaybackSupportedByDevice) {
-                // Attempt to run the attentional haptic ringtone first and fallback to the default
-                // vibration effect if hapticFuture is completed with false.
-                hapticsFuture = mRingtonePlayer.play(mRingtoneFactory, foregroundCall, null,
-                        attributes.isRingerAudible(), isVibratorEnabled);
-            }
-            effect = mDefaultVibrationEffect;
-        }
-
-        if (hapticsFuture != null) {
-            final boolean shouldRingForContact = attributes.shouldRingForContact();
-            final boolean isRingerAudible = attributes.isRingerAudible();
-            mVibrateFuture = hapticsFuture.thenAccept(isUsingAudioCoupledHaptics -> {
-                if (!isUsingAudioCoupledHaptics || !mIsHapticPlaybackSupportedByDevice) {
-                    Log.i(this, "startRinging: fileHasHaptics=%b, hapticsSupported=%b",
-                            isUsingAudioCoupledHaptics, mIsHapticPlaybackSupportedByDevice);
-                    maybeStartVibration(foregroundCall, shouldRingForContact, effect,
-                            isVibratorEnabled, isRingerAudible);
-                } else if (shouldApplyRampingRinger
-                        && !mSystemSettingsUtil.isAudioCoupledVibrationForRampingRingerEnabled()) {
-                    Log.i(this, "startRinging: apply ramping ringer vibration");
-                    maybeStartVibration(foregroundCall, shouldRingForContact, effect,
-                            isVibratorEnabled, isRingerAudible);
-                } else {
-                    Log.addEvent(foregroundCall, LogUtils.Events.SKIP_VIBRATION,
-                            "using audio-coupled haptics");
-                }
-            });
-            if (mBlockOnRingingFuture != null) {
-                mVibrateFuture.whenComplete((v, e) -> mBlockOnRingingFuture.complete(null));
-            }
-        } else {
-            if (mBlockOnRingingFuture != null) {
-                mBlockOnRingingFuture.complete(null);
-            }
-            Log.w(this, "startRinging: No haptics future; fallback to default behavior");
-            maybeStartVibration(foregroundCall, attributes.shouldRingForContact(), effect,
-                    isVibratorEnabled, attributes.isRingerAudible());
-        }
-
-        return attributes.shouldAcquireAudioFocus();
+    @VisibleForTesting
+    public void setNotificationManager(NotificationManager notificationManager) {
+        mNotificationManager = notificationManager;
     }
 
-    private void maybeStartVibration(Call foregroundCall, boolean shouldRingForContact,
-        VibrationEffect effect, boolean isVibrationEnabled, boolean isRingerAudible) {
-        synchronized (mLock) {
-            mAudioManager = mContext.getSystemService(AudioManager.class);
-            if (isVibrationEnabled && !mIsVibrating && shouldRingForContact) {
-                Log.addEvent(foregroundCall, LogUtils.Events.START_VIBRATOR,
-                        "hasVibrator=%b, userRequestsVibrate=%b, ringerMode=%d, isVibrating=%b",
-                        mVibrator.hasVibrator(),
-                        mSystemSettingsUtil.isRingVibrationEnabled(mContext),
-                        mAudioManager.getRingerMode(), mIsVibrating);
-                if (mSystemSettingsUtil.isRampingRingerEnabled(mContext) && isRingerAudible) {
-                    Log.i(this, "start vibration for ramping ringer.");
-                } else {
-                    Log.i(this, "start normal vibration.");
+    public boolean startRinging(Call foregroundCall, boolean isHfpDeviceAttached) {
+        boolean deferBlockOnRingingFuture = false;
+        // try-finally to ensure that the block on ringing future is always called.
+        try {
+            if (foregroundCall == null) {
+                Log.wtf(this, "startRinging called with null foreground call.");
+                return false;
+            }
+
+            if (foregroundCall.getState() != CallState.RINGING
+                    && foregroundCall.getState() != CallState.SIMULATED_RINGING) {
+                // It's possible for bluetooth to connect JUST as a call goes active, which would
+                // mean the call would start ringing again.
+                Log.i(this, "startRinging called for non-ringing foreground callid=%s",
+                        foregroundCall.getId());
+                return false;
+            }
+
+            // Use completable future to establish a timeout, not intent to make these work outside
+            // the main thread asynchronously
+            // TODO: moving these RingerAttributes calculation out of Telecom lock to avoid blocking
+            CompletableFuture<RingerAttributes> ringerAttributesFuture = CompletableFuture
+                    .supplyAsync(() -> getRingerAttributes(foregroundCall, isHfpDeviceAttached),
+                            new LoggedHandlerExecutor(getHandler(), "R.sR", null));
+
+            RingerAttributes attributes = null;
+            try {
+                mAttributesLatch = new CountDownLatch(1);
+                attributes = ringerAttributesFuture.get(
+                        RINGER_ATTRIBUTES_TIMEOUT, TimeUnit.MILLISECONDS);
+            } catch (ExecutionException | InterruptedException | TimeoutException e) {
+                // Keep attributes as null
+                Log.i(this, "getAttributes error: " + e);
+            }
+
+            if (attributes == null) {
+                Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING,
+                        "RingerAttributes error");
+                return false;
+            }
+
+            if (attributes.isEndEarly()) {
+                boolean acquireAudioFocus = attributes.shouldAcquireAudioFocus();
+                if (attributes.letDialerHandleRinging()) {
+                    Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Dialer handles");
+                    // Dialer will setup a ringtone, provide the audio focus if its audible.
+                    acquireAudioFocus |= attributes.isRingerAudible();
                 }
+
+                if (attributes.isSilentRingingRequested()) {
+                    Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Silent ringing "
+                            + "requested");
+                }
+                if (attributes.isWorkProfileInQuietMode()) {
+                    Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING,
+                            "Work profile in quiet mode");
+                }
+                return acquireAudioFocus;
+            }
+
+            stopCallWaiting();
+
+            final boolean shouldFlash = attributes.shouldRingForContact();
+            if (mAccessibilityManagerAdapter != null && shouldFlash) {
+                Log.addEvent(foregroundCall, LogUtils.Events.FLASH_NOTIFICATION_START);
+                getHandler().post(() ->
+                        mAccessibilityManagerAdapter.startFlashNotificationSequence(mContext,
+                                AccessibilityManager.FLASH_REASON_CALL));
+            }
+
+            // Determine if the settings and DND mode indicate that the vibrator can be used right
+            // now.
+            final boolean isVibratorEnabled =
+                    isVibratorEnabled(mContext, attributes.shouldRingForContact());
+            boolean shouldApplyRampingRinger =
+                    isVibratorEnabled && mSystemSettingsUtil.isRampingRingerEnabled(mContext);
+
+            boolean isHapticOnly = false;
+            boolean useCustomVibrationEffect = false;
+
+            mVolumeShaperConfig = null;
+
+            if (attributes.isRingerAudible()) {
+                mRingingCall = foregroundCall;
+                Log.addEvent(foregroundCall, LogUtils.Events.START_RINGER);
+                // Because we wait until a contact info query to complete before processing a
+                // call (for the purposes of direct-to-voicemail), the information about custom
+                // ringtones should be available by the time this code executes. We can safely
+                // request the custom ringtone from the call and expect it to be current.
+                if (shouldApplyRampingRinger) {
+                    Log.i(this, "create ramping ringer.");
+                    float silencePoint = (float) (RAMPING_RINGER_VIBRATION_DURATION)
+                            / (float) (RAMPING_RINGER_VIBRATION_DURATION + RAMPING_RINGER_DURATION);
+                    mVolumeShaperConfig =
+                            new VolumeShaper.Configuration.Builder()
+                                    .setDuration(RAMPING_RINGER_VIBRATION_DURATION
+                                            + RAMPING_RINGER_DURATION)
+                                    .setCurve(
+                                            new float[]{0.f, silencePoint + EPSILON
+                                                    /*keep monotonicity*/, 1.f},
+                                            new float[]{0.f, 0.f, 1.f})
+                                    .setInterpolatorType(
+                                            VolumeShaper.Configuration.INTERPOLATOR_TYPE_LINEAR)
+                                    .build();
+                    if (mSystemSettingsUtil.isAudioCoupledVibrationForRampingRingerEnabled()) {
+                        useCustomVibrationEffect = true;
+                    }
+                } else {
+                    if (DEBUG_RINGER) {
+                        Log.i(this, "Create ringer with custom vibration effect");
+                    }
+                    // Ramping ringtone is not enabled.
+                    useCustomVibrationEffect = true;
+                }
+            } else {
+                Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING,
+                        "Inaudible: " + attributes.getInaudibleReason()
+                                + " isVibratorEnabled=" + isVibratorEnabled);
+
+                if (isVibratorEnabled) {
+                    // If ringer is not audible for this call, then the phone is in "Vibrate" mode.
+                    // Use haptic-only ringtone or do not play anything.
+                    isHapticOnly = true;
+                    if (DEBUG_RINGER) {
+                        Log.i(this, "Set ringtone as haptic only: " + isHapticOnly);
+                    }
+                } else {
+                    foregroundCall.setUserMissed(USER_MISSED_NO_VIBRATE);
+                    return attributes.shouldAcquireAudioFocus(); // ringer not audible
+                }
+            }
+
+            boolean hapticChannelsMuted = !isVibratorEnabled || !mIsHapticPlaybackSupportedByDevice;
+            if (shouldApplyRampingRinger
+                    && !mSystemSettingsUtil.isAudioCoupledVibrationForRampingRingerEnabled()
+                    && isVibratorEnabled) {
+                Log.i(this, "Muted haptic channels since audio coupled ramping ringer is disabled");
+                hapticChannelsMuted = true;
+            } else if (hapticChannelsMuted) {
+                Log.i(this,
+                        "Muted haptic channels isVibratorEnabled=%s, hapticPlaybackSupported=%s",
+                        isVibratorEnabled, mIsHapticPlaybackSupportedByDevice);
+            }
+            // Defer ringtone creation to the async player thread.
+            Supplier<Ringtone> ringtoneSupplier;
+            final boolean finalHapticChannelsMuted = hapticChannelsMuted;
+            if (isHapticOnly) {
+                if (hapticChannelsMuted) {
+                    Log.i(this,
+                            "want haptic only ringtone but haptics are muted, skip ringtone play");
+                    ringtoneSupplier = null;
+                } else {
+                    ringtoneSupplier = mRingtoneFactory::getHapticOnlyRingtone;
+                }
+            } else {
+                ringtoneSupplier = () -> mRingtoneFactory.getRingtone(
+                        foregroundCall, mVolumeShaperConfig, finalHapticChannelsMuted);
+            }
+
+            // The vibration logic depends on the loaded ringtone, but we need to defer the ringtone
+            // load to the async ringtone thread. Hence, we bundle up the final part of this method
+            // for that thread to run after loading the ringtone. This logic is intended to run even
+            // if the loaded ringtone is null. However if a stop event arrives before the ringtone
+            // creation finishes, then this consumer can be skipped.
+            final boolean finalUseCustomVibrationEffect = useCustomVibrationEffect;
+            final RingerAttributes finalAttributes = attributes;
+            BiConsumer<Ringtone, Boolean> vibrationLogic = (Ringtone ringtone, Boolean stopped) -> {
+                try {
+                    if (stopped.booleanValue()) {
+                        return;  // don't start vibration if the ringing is already abandoned.
+                    }
+                    final VibrationEffect vibrationEffect;
+                    if (ringtone != null && finalUseCustomVibrationEffect) {
+                        if (DEBUG_RINGER) {
+                            Log.d(this, "Using ringtone defined vibration effect.");
+                        }
+                        vibrationEffect = getVibrationEffectForRingtone(ringtone);
+                    } else {
+                        vibrationEffect = mDefaultVibrationEffect;
+                    }
+
+                    boolean isUsingAudioCoupledHaptics =
+                            !finalHapticChannelsMuted && ringtone != null
+                                    && ringtone.hasHapticChannels();
+                    vibrateIfNeeded(isUsingAudioCoupledHaptics, finalAttributes, foregroundCall,
+                            vibrationEffect, isVibratorEnabled);
+                } finally {
+                    // This is used to signal to tests that the async play() call has completed.
+                    if (mBlockOnRingingFuture != null) {
+                        mBlockOnRingingFuture.complete(null);
+                    }
+                }
+            };
+            deferBlockOnRingingFuture = true;  // Run in vibrationLogic.
+            if (ringtoneSupplier != null) {
+                mRingtonePlayer.play(ringtoneSupplier, vibrationLogic);
+            } else {
+                vibrationLogic.accept(/* ringtone= */ null, /* stopped= */ false);
+            }
+
+            // shouldAcquireAudioFocus is meant to be true, but that check is deferred to here
+            // because until now is when we actually know if the ringtone loading worked.
+            return attributes.shouldAcquireAudioFocus()
+                    || (!isHapticOnly && attributes.isRingerAudible());
+        } finally {
+            // This is used to signal to tests that the async play() call has completed. It can
+            // be deferred into AsyncRingtonePlayer
+            if (mBlockOnRingingFuture != null && !deferBlockOnRingingFuture) {
+                mBlockOnRingingFuture.complete(null);
+            }
+        }
+    }
+
+    private void vibrateIfNeeded(boolean isUsingAudioCoupledHaptics, RingerAttributes attributes,
+        Call foregroundCall, VibrationEffect effect, boolean isVibratorEnabled) {
+        final boolean shouldRingForContact = attributes.shouldRingForContact();
+
+        if (isUsingAudioCoupledHaptics) {
+            Log.addEvent(
+                foregroundCall, LogUtils.Events.SKIP_VIBRATION, "using audio-coupled haptics");
+            return;
+        }
+
+        synchronized (mLock) {
+            if (isVibratorEnabled && !mIsVibrating && shouldRingForContact) {
+                Log.addEvent(foregroundCall, LogUtils.Events.START_VIBRATOR,
+                    "hasVibrator=%b, userRequestsVibrate=%b, ringerMode=%d, isVibrating=%b",
+                    mVibrator.hasVibrator(), mSystemSettingsUtil.isRingVibrationEnabled(mContext),
+                    mAudioManager.getRingerMode(), mIsVibrating);
+                mVibratingCall = foregroundCall;
                 mIsVibrating = true;
                 mVibrator.vibrate(effect, VIBRATION_ATTRIBUTES);
+                Log.i(this, "start vibration.");
             } else {
                 foregroundCall.setUserMissed(USER_MISSED_NO_VIBRATE);
                 Log.addEvent(foregroundCall, LogUtils.Events.SKIP_VIBRATION,
-                        "hasVibrator=%b, userRequestsVibrate=%b, ringerMode=%d, isVibrating=%b",
-                        mVibrator.hasVibrator(),
-                        mSystemSettingsUtil.isRingVibrationEnabled(mContext),
-                        mAudioManager.getRingerMode(), mIsVibrating);
+                    "hasVibrator=%b, userRequestsVibrate=%b, ringerMode=%d, isVibrating=%b",
+                    mVibrator.hasVibrator(), mSystemSettingsUtil.isRingVibrationEnabled(mContext),
+                    mAudioManager.getRingerMode(), mIsVibrating);
             }
         }
     }
 
-    private VibrationEffect getVibrationEffectForCall(RingtoneFactory factory, Call call) {
-        VibrationEffect effect = null;
-        Ringtone ringtone = factory.getRingtone(call);
-        Uri ringtoneUri = ringtone != null ? ringtone.getUri() : null;
-        if (ringtoneUri != null) {
-            try {
-                effect = mVibrationEffectProxy.get(ringtoneUri, mContext);
-            } catch (IllegalArgumentException iae) {
-                // Deep in the bowels of the VibrationEffect class it is possible for an
-                // IllegalArgumentException to be thrown if there is an invalid URI specified in the
-                // device config, or a content provider failure.  Rather than crashing the Telecom
-                // process we will just use the default vibration effect.
-                Log.e(this, iae, "getVibrationEffectForCall: failed to get vibration effect");
-                effect = null;
+    private VibrationEffect getVibrationEffectForRingtone(@NonNull Ringtone ringtone) {
+        Uri ringtoneUri = ringtone.getUri();
+        if (ringtoneUri == null) {
+            return mDefaultVibrationEffect;
+        }
+        try {
+            VibrationEffect effect = mVibrationEffectProxy.get(ringtoneUri, mContext);
+            if (effect == null) {
+              Log.i(this, "did not find vibration effect, falling back to default vibration");
+              return mDefaultVibrationEffect;
             }
+            return effect;
+        } catch (IllegalArgumentException iae) {
+            // Deep in the bowels of the VibrationEffect class it is possible for an
+            // IllegalArgumentException to be thrown if there is an invalid URI specified in the
+            // device config, or a content provider failure.  Rather than crashing the Telecom
+            // process we will just use the default vibration effect.
+            Log.e(this, iae, "getVibrationEffectForRingtone: failed to get vibration effect");
+            return mDefaultVibrationEffect;
         }
-
-        if (effect == null) {
-            effect = mDefaultVibrationEffect;
-        }
-        return effect;
     }
 
     public void startCallWaiting(Call call) {
@@ -419,7 +521,8 @@
             return;
         }
 
-        if (mInCallController.doesConnectedDialerSupportRinging()) {
+        if (mInCallController.doesConnectedDialerSupportRinging(
+                call.getUserHandleFromTargetPhoneAccount())) {
             Log.addEvent(call, LogUtils.Events.SKIP_RINGING, "Dialer handles");
             return;
         }
@@ -443,6 +546,13 @@
     }
 
     public void stopRinging() {
+        final Call foregroundCall = mRingingCall != null ? mRingingCall : mVibratingCall;
+        if (mAccessibilityManagerAdapter != null) {
+            Log.addEvent(foregroundCall, LogUtils.Events.FLASH_NOTIFICATION_STOP);
+            getHandler().post(() ->
+                    mAccessibilityManagerAdapter.stopFlashNotificationSequence(mContext));
+        }
+
         synchronized (mLock) {
             if (mRingingCall != null) {
                 Log.addEvent(mRingingCall, LogUtils.Events.STOP_RINGER);
@@ -451,12 +561,6 @@
 
             mRingtonePlayer.stop();
 
-            // If we haven't started vibrating because we were waiting for the haptics info, cancel
-            // it and don't vibrate at all.
-            if (mVibrateFuture != null) {
-                mVibrateFuture.cancel(true);
-            }
-
             if (mIsVibrating) {
                 Log.addEvent(mVibratingCall, LogUtils.Events.STOP_VIBRATOR);
                 mVibrator.cancel();
@@ -483,16 +587,33 @@
         return mRingtonePlayer.isPlaying();
     }
 
-    private boolean shouldRingForContact(Uri contactUri) {
-        final NotificationManager manager =
-                (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+    /**
+     * shouldRingForContact checks if the caller matches one of the Do Not Disturb bypass
+     * settings (ex. A contact or repeat caller might be able to bypass DND settings). If
+     * matchesCallFilter returns true, this means the caller can bypass the Do Not Disturb settings
+     * and interrupt the user; otherwise call is suppressed.
+     */
+    public boolean shouldRingForContact(Call call) {
+        // avoid re-computing manager.matcherCallFilter(Bundle)
+        if (call.wasDndCheckComputedForCall()) {
+            Log.v(this, "shouldRingForContact: returning computation from DndCallFilter.");
+            return !call.isCallSuppressedByDoNotDisturb();
+        }
+
+        final Uri contactUri = call.getHandle();
         final Bundle peopleExtras = new Bundle();
         if (contactUri != null) {
             ArrayList<Person> personList = new ArrayList<>();
             personList.add(new Person.Builder().setUri(contactUri.toString()).build());
             peopleExtras.putParcelableArrayList(Notification.EXTRA_PEOPLE_LIST, personList);
         }
-        return manager.matchesCallFilter(peopleExtras);
+
+        // query NotificationManager
+        boolean shouldRing = mNotificationManager.matchesCallFilter(peopleExtras);
+        // store the suppressed status in the call object
+        call.setCallIsSuppressedByDoNotDisturb(!shouldRing);
+
+        return shouldRing;
     }
 
     private boolean hasExternalRinger(Call foregroundCall) {
@@ -508,10 +629,8 @@
         AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
         // Use AudioManager#getRingerMode for more accurate result, instead of
         // AudioManager#getRingerModeInternal which only useful for volume controllers
-        NotificationManager notificationManager = context.getSystemService(
-                NotificationManager.class);
-        boolean zenModeOn = notificationManager != null
-                && notificationManager.getZenMode() != ZEN_MODE_OFF;
+        boolean zenModeOn = mNotificationManager != null
+                && mNotificationManager.getZenMode() != ZEN_MODE_OFF;
         return mVibrator.hasVibrator()
                 && mSystemSettingsUtil.isRingVibrationEnabled(context)
                 && (audioManager.getRingerMode() != AudioManager.RINGER_MODE_SILENT
@@ -526,22 +645,19 @@
 
         boolean isVolumeOverZero = mAudioManager.getStreamVolume(AudioManager.STREAM_RING) > 0;
         timer.record("isVolumeOverZero");
-        boolean shouldRingForContact = shouldRingForContact(call.getHandle());
+        boolean shouldRingForContact = shouldRingForContact(call);
         timer.record("shouldRingForContact");
-        boolean isRingtonePresent = !(mRingtoneFactory.getRingtone(call) == null);
-        timer.record("getRingtone");
         boolean isSelfManaged = call.isSelfManaged();
         timer.record("isSelfManaged");
         boolean isSilentRingingRequested = call.isSilentRingingRequested();
         timer.record("isSilentRingRequested");
 
-        boolean isRingerAudible = isVolumeOverZero && shouldRingForContact && isRingtonePresent;
+        boolean isRingerAudible = isVolumeOverZero && shouldRingForContact;
         timer.record("isRingerAudible");
         String inaudibleReason = "";
         if (!isRingerAudible) {
-            inaudibleReason = String.format(
-                    "isVolumeOverZero=%s, shouldRingForContact=%s, isRingtonePresent=%s",
-                    isVolumeOverZero, shouldRingForContact, isRingtonePresent);
+            inaudibleReason = String.format("isVolumeOverZero=%s, shouldRingForContact=%s",
+                isVolumeOverZero, shouldRingForContact);
         }
 
         boolean hasExternalRinger = hasExternalRinger(call);
@@ -549,27 +665,32 @@
         // Don't do call waiting operations or vibration unless these are false.
         boolean isTheaterModeOn = mSystemSettingsUtil.isTheaterModeOn(mContext);
         timer.record("isTheaterModeOn");
-        boolean letDialerHandleRinging = mInCallController.doesConnectedDialerSupportRinging();
+        boolean letDialerHandleRinging = mInCallController.doesConnectedDialerSupportRinging(
+                call.getUserHandleFromTargetPhoneAccount());
         timer.record("letDialerHandleRinging");
+        boolean isWorkProfileInQuietMode =
+                isProfileInQuietMode(call.getUserHandleFromTargetPhoneAccount());
+        timer.record("isWorkProfileInQuietMode");
 
         Log.i(this, "startRinging timings: " + timer);
         boolean endEarly = isTheaterModeOn || letDialerHandleRinging || isSelfManaged ||
-                hasExternalRinger || isSilentRingingRequested;
+                hasExternalRinger || isSilentRingingRequested || isWorkProfileInQuietMode;
 
         if (endEarly) {
             Log.i(this, "Ending early -- isTheaterModeOn=%s, letDialerHandleRinging=%s, " +
-                            "isSelfManaged=%s, hasExternalRinger=%s, silentRingingRequested=%s",
+                            "isSelfManaged=%s, hasExternalRinger=%s, silentRingingRequested=%s, " +
+                            "isWorkProfileInQuietMode=%s",
                     isTheaterModeOn, letDialerHandleRinging, isSelfManaged, hasExternalRinger,
-                    isSilentRingingRequested);
+                    isSilentRingingRequested, isWorkProfileInQuietMode);
         }
 
         // Acquire audio focus under any of the following conditions:
         // 1. Should ring for contact and there's an HFP device attached
         // 2. Volume is over zero, we should ring for the contact, and there's a audible ringtone
-        //    present.
+        //    present. (This check is deferred until ringer knows the ringtone)
         // 3. The call is self-managed.
-        boolean shouldAcquireAudioFocus =
-                isRingerAudible || (isHfpDeviceAttached && shouldRingForContact) || isSelfManaged;
+        boolean shouldAcquireAudioFocus = !isWorkProfileInQuietMode &&
+                ((isHfpDeviceAttached && shouldRingForContact) || isSelfManaged);
 
         // Set missed reason according to attributes
         if (!isVolumeOverZero) {
@@ -587,9 +708,15 @@
                 .setInaudibleReason(inaudibleReason)
                 .setShouldRingForContact(shouldRingForContact)
                 .setSilentRingingRequested(isSilentRingingRequested)
+                .setWorkProfileQuietMode(isWorkProfileInQuietMode)
                 .build();
     }
 
+    private boolean isProfileInQuietMode(UserHandle user) {
+        UserManager um = mContext.getSystemService(UserManager.class);
+        return um.isManagedProfile(user.getIdentifier()) && um.isQuietModeEnabled(user);
+    }
+
     private Handler getHandler() {
         if (mHandler == null) {
             HandlerThread handlerThread = new HandlerThread("Ringer");
diff --git a/src/com/android/server/telecom/RingerAttributes.java b/src/com/android/server/telecom/RingerAttributes.java
index 840d815..e0d3e1c 100644
--- a/src/com/android/server/telecom/RingerAttributes.java
+++ b/src/com/android/server/telecom/RingerAttributes.java
@@ -25,6 +25,7 @@
         private String mInaudibleReason;
         private boolean mShouldRingForContact;
         private boolean mSilentRingingRequested;
+        private boolean mWorkProfileQuietMode;
 
         public RingerAttributes.Builder setEndEarly(boolean endEarly) {
             mEndEarly = endEarly;
@@ -61,10 +62,15 @@
             return this;
         }
 
+        public RingerAttributes.Builder setWorkProfileQuietMode(boolean workProfileQuietMode) {
+            mWorkProfileQuietMode = workProfileQuietMode;
+            return this;
+        }
+
         public RingerAttributes build() {
             return new RingerAttributes(mEndEarly, mLetDialerHandleRinging, mAcquireAudioFocus,
                     mRingerAudible, mInaudibleReason, mShouldRingForContact,
-                    mSilentRingingRequested);
+                    mSilentRingingRequested, mWorkProfileQuietMode);
         }
     }
 
@@ -75,10 +81,12 @@
     private String mInaudibleReason;
     private boolean mShouldRingForContact;
     private boolean mSilentRingingRequested;
+    private boolean mWorkProfileQuietMode;
 
     private RingerAttributes(boolean endEarly, boolean letDialerHandleRinging,
             boolean acquireAudioFocus, boolean ringerAudible, String inaudibleReason,
-            boolean shouldRingForContact, boolean silentRingingRequested) {
+            boolean shouldRingForContact, boolean silentRingingRequested,
+            boolean workProfileQuietMode) {
         mEndEarly = endEarly;
         mLetDialerHandleRinging = letDialerHandleRinging;
         mAcquireAudioFocus = acquireAudioFocus;
@@ -86,6 +94,7 @@
         mInaudibleReason = inaudibleReason;
         mShouldRingForContact = shouldRingForContact;
         mSilentRingingRequested = silentRingingRequested;
+        mWorkProfileQuietMode = workProfileQuietMode;
     }
 
     public boolean isEndEarly() {
@@ -115,4 +124,8 @@
     public boolean isSilentRingingRequested() {
         return mSilentRingingRequested;
     }
+
+    public boolean isWorkProfileInQuietMode() {
+        return mWorkProfileQuietMode;
+    }
 }
diff --git a/src/com/android/server/telecom/RingtoneFactory.java b/src/com/android/server/telecom/RingtoneFactory.java
index 5c46998..f438ff8 100644
--- a/src/com/android/server/telecom/RingtoneFactory.java
+++ b/src/com/android/server/telecom/RingtoneFactory.java
@@ -65,7 +65,12 @@
     }
 
     public Ringtone getRingtone(Call incomingCall,
-            @Nullable VolumeShaper.Configuration volumeShaperConfig) {
+            @Nullable VolumeShaper.Configuration volumeShaperConfig, boolean hapticChannelsMuted) {
+        // Initializing ringtones on the main thread can deadlock
+        ThreadUtil.checkNotOnMainThread();
+
+        AudioAttributes audioAttrs = getDefaultRingtoneAudioAttributes(hapticChannelsMuted);
+
         // Use the default ringtone of the work profile if the contact is a work profile contact.
         Context userContext = isWorkContact(incomingCall) ?
                 getWorkProfileContextForUser(mCallsManager.getCurrentUserHandle()) :
@@ -73,18 +78,16 @@
         Uri ringtoneUri = incomingCall.getRingtone();
         Ringtone ringtone = null;
 
-        AudioAttributes audioAttrs = getRingtoneAudioAttributes();
-
-        if(ringtoneUri != null && userContext != null) {
+        if (ringtoneUri != null && userContext != null) {
             // Ringtone URI is explicitly specified. First, try to create a Ringtone with that.
             try {
-              ringtone = RingtoneManager.getRingtone(
-                  userContext, ringtoneUri, volumeShaperConfig, audioAttrs);
-            } catch (NullPointerException npe) {
-                Log.e(this, npe, "getRingtone: NPE while getting ringtone.");
+                ringtone = RingtoneManager.getRingtone(
+                        userContext, ringtoneUri, volumeShaperConfig, audioAttrs);
+            } catch (Exception e) {
+                Log.e(this, e, "getRingtone: exception while getting ringtone.");
             }
         }
-        if(ringtone == null) {
+        if (ringtone == null) {
             // Contact didn't specify ringtone or custom Ringtone creation failed. Get default
             // ringtone for user or profile.
             Context contextToUse = hasDefaultRingtoneForUser(userContext) ? userContext : mContext;
@@ -101,37 +104,40 @@
                     Log.i(this, "getRingtone: Settings.System.DEFAULT_RINGTONE_URI is null.");
                 }
             }
+
             if (defaultRingtoneUri == null) {
                 return null;
             }
+
             try {
                 ringtone = RingtoneManager.getRingtone(
-                    contextToUse, defaultRingtoneUri, volumeShaperConfig, audioAttrs);
-            } catch (NullPointerException npe) {
-                Log.e(this, npe, "getRingtone: NPE while getting ringtone.");
+                        contextToUse, defaultRingtoneUri, volumeShaperConfig, audioAttrs);
+            } catch (Exception e) {
+                Log.e(this, e, "getRingtone: exception while getting ringtone.");
             }
         }
         return ringtone;
     }
 
-    public AudioAttributes getRingtoneAudioAttributes() {
+    private AudioAttributes getDefaultRingtoneAudioAttributes(boolean hapticChannelsMuted) {
         return new AudioAttributes.Builder()
             .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
             .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+            .setHapticChannelsMuted(hapticChannelsMuted)
             .build();
     }
 
-    public Ringtone getRingtone(Call incomingCall) {
-        return getRingtone(incomingCall, null);
-    }
-
     /** Returns a ringtone to be used when ringer is not audible for the incoming call. */
     @Nullable
     public Ringtone getHapticOnlyRingtone() {
+        // Initializing ringtones on the main thread can deadlock
+        ThreadUtil.checkNotOnMainThread();
         Uri ringtoneUri = Uri.parse("file://" + mContext.getString(
                 com.android.internal.R.string.config_defaultRingtoneVibrationSound));
-        AudioAttributes audioAttrs = getRingtoneAudioAttributes();
-        Ringtone ringtone = RingtoneManager.getRingtone(mContext, ringtoneUri, null, audioAttrs);
+        AudioAttributes audioAttrs = getDefaultRingtoneAudioAttributes(
+            /* hapticChannelsMuted */ false);
+        Ringtone ringtone = RingtoneManager.getRingtone(
+            mContext, ringtoneUri, /* volumeShaperConfig */ null, audioAttrs);
         if (ringtone != null) {
             // Make sure the sound is muted.
             ringtone.setVolume(0);
diff --git a/src/com/android/server/telecom/RoleManagerAdapter.java b/src/com/android/server/telecom/RoleManagerAdapter.java
index ba82a06..8fdfb11 100644
--- a/src/com/android/server/telecom/RoleManagerAdapter.java
+++ b/src/com/android/server/telecom/RoleManagerAdapter.java
@@ -41,7 +41,7 @@
      * redirection role.
      * @return the package name of the app filling the role, {@code null} otherwise}.
      */
-    String getDefaultCallRedirectionApp();
+    String getDefaultCallRedirectionApp(UserHandle userHandle);
 
     /**
      * Override the {@link android.app.role.RoleManager} call redirection app with another value.
@@ -56,7 +56,7 @@
      * screening role.
      * @return the package name of the app filling the role, {@code null} otherwise}.
      */
-    String getDefaultCallScreeningApp();
+    String getDefaultCallScreeningApp(UserHandle userHandle);
 
     /**
      * Override the {@link android.app.role.RoleManager} call screening app with another value.
diff --git a/src/com/android/server/telecom/RoleManagerAdapterImpl.java b/src/com/android/server/telecom/RoleManagerAdapterImpl.java
index 4a98d7b..ac35b3d 100644
--- a/src/com/android/server/telecom/RoleManagerAdapterImpl.java
+++ b/src/com/android/server/telecom/RoleManagerAdapterImpl.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.os.Binder;
 import android.os.UserHandle;
 import android.telecom.Log;
 
@@ -50,11 +51,11 @@
     }
 
     @Override
-    public String getDefaultCallRedirectionApp() {
+    public String getDefaultCallRedirectionApp(UserHandle userHandleForCallRedirection) {
         if (mOverrideDefaultCallRedirectionApp != null) {
             return mOverrideDefaultCallRedirectionApp;
         }
-        return getRoleManagerCallRedirectionApp();
+        return getRoleManagerCallRedirectionApp(userHandleForCallRedirection);
     }
 
     @Override
@@ -63,11 +64,11 @@
     }
 
     @Override
-    public String getDefaultCallScreeningApp() {
+    public String getDefaultCallScreeningApp(UserHandle userHandleForCallScreening) {
         if (mOverrideDefaultCallScreeningApp != null) {
             return mOverrideDefaultCallScreeningApp;
         }
-        return getRoleManagerCallScreeningApp();
+        return getRoleManagerCallScreeningApp(userHandleForCallScreening);
     }
 
     @Override
@@ -118,9 +119,9 @@
         mCurrentUserHandle = currentUserHandle;
     }
 
-    private String getRoleManagerCallScreeningApp() {
+    private String getRoleManagerCallScreeningApp(UserHandle userHandle) {
         List<String> roleHolders = mRoleManager.getRoleHoldersAsUser(ROLE_CALL_SCREENING,
-                mCurrentUserHandle);
+                userHandle);
         if (roleHolders == null || roleHolders.isEmpty()) {
             return null;
         }
@@ -141,9 +142,9 @@
         return new ArrayList<>();
     }
 
-    private String getRoleManagerCallRedirectionApp() {
+    private String getRoleManagerCallRedirectionApp(UserHandle userHandle) {
         List<String> roleHolders = mRoleManager.getRoleHoldersAsUser(ROLE_CALL_REDIRECTION_APP,
-                mCurrentUserHandle);
+                userHandle);
         if (roleHolders == null || roleHolders.isEmpty()) {
             return null;
         }
@@ -184,7 +185,7 @@
             pw.print("(override ");
             pw.print(mOverrideDefaultCallRedirectionApp);
             pw.print(") ");
-            pw.print(getRoleManagerCallRedirectionApp());
+            pw.print(getRoleManagerCallRedirectionApp(Binder.getCallingUserHandle()));
         }
         pw.println();
 
@@ -193,7 +194,7 @@
             pw.print("(override ");
             pw.print(mOverrideDefaultCallScreeningApp);
             pw.print(") ");
-            pw.print(getRoleManagerCallScreeningApp());
+            pw.print(getRoleManagerCallScreeningApp(Binder.getCallingUserHandle()));
         }
         pw.println();
 
diff --git a/src/com/android/server/telecom/StatusBarNotifier.java b/src/com/android/server/telecom/StatusBarNotifier.java
index af3493e..772335e 100644
--- a/src/com/android/server/telecom/StatusBarNotifier.java
+++ b/src/com/android/server/telecom/StatusBarNotifier.java
@@ -29,7 +29,7 @@
  */
 @VisibleForTesting
 public class StatusBarNotifier extends CallsManagerListenerBase {
-    private static final String SLOT_MUTE = "mute";
+    private static final String SLOT_MUTE = "telecom_mute";
     private static final String SLOT_SPEAKERPHONE = "speakerphone";
 
     private final Context mContext;
@@ -79,13 +79,15 @@
         mIsShowingMute = isMuted;
     }
 
+    /**
+     * Update the status bar manager with the new speakerphone state.
+     *
+     * IMPORTANT: DO NOT call into any Telecom code here; this is usually scheduled on an async
+     * executor to save Telecom from blocking on outgoing binder calls.
+     * @param isSpeakerphone
+     */
     @VisibleForTesting
     public void notifySpeakerphone(boolean isSpeakerphone) {
-        // Never display anything if there are no calls.
-        if (!mCallsManager.hasAnyCalls()) {
-            isSpeakerphone = false;
-        }
-
         if (mIsShowingSpeakerphone == isSpeakerphone) {
             return;
         }
diff --git a/src/com/android/server/telecom/StreamingCallAdapter.java b/src/com/android/server/telecom/StreamingCallAdapter.java
new file mode 100644
index 0000000..e899aff
--- /dev/null
+++ b/src/com/android/server/telecom/StreamingCallAdapter.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2022 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.server.telecom;
+
+import android.os.Binder;
+import android.os.RemoteException;
+import android.telecom.DisconnectCause;
+import android.telecom.Log;
+import android.telecom.StreamingCall;
+
+import com.android.internal.telecom.IStreamingCallAdapter;
+
+/**
+ * Receives call commands and updates from general call streaming app and passes them through to
+ * the original voip call app. {@link android.telecom.CallStreamingService} creates an instance of
+ * this class and passes it to the general call streaming app after binding to it. This adapter can
+ * receive commands and updates until the general call streaming app is unbound.
+ */
+public class StreamingCallAdapter extends IStreamingCallAdapter.Stub {
+    private final static String TAG = "StreamingCallAdapter";
+
+    private final TransactionalServiceWrapper mTransactionalServiceWrapper;
+    private final Call mCall;
+    private final String mOwnerPackageAbbreviation;
+
+    public StreamingCallAdapter(TransactionalServiceWrapper wrapper, Call call,
+            String ownerPackageName) {
+        mTransactionalServiceWrapper = wrapper;
+        mCall = call;
+        mOwnerPackageAbbreviation = Log.getPackageAbbreviation(ownerPackageName);
+    }
+
+    @Override
+    public void setStreamingState(int state) throws RemoteException {
+        try {
+            Log.startSession(LogUtils.Sessions.CSA_SET_STATE, mOwnerPackageAbbreviation);
+            long token = Binder.clearCallingIdentity();
+            try {
+                Log.i(this, "setStreamingState(%d)", state);
+                switch (state) {
+                    case StreamingCall.STATE_STREAMING:
+                        mTransactionalServiceWrapper.onSetActive(mCall);
+                    case StreamingCall.STATE_HOLDING:
+                        mTransactionalServiceWrapper.onSetInactive(mCall);
+                    case StreamingCall.STATE_DISCONNECTED:
+                        mTransactionalServiceWrapper.onDisconnect(mCall,
+                                new DisconnectCause(DisconnectCause.LOCAL));
+                    default:
+                        // ignore
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        } finally {
+            Log.endSession();
+        }
+    }
+}
diff --git a/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java b/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
index e1f2d08..0be90e0 100644
--- a/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
+++ b/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
@@ -16,10 +16,12 @@
 
 package com.android.server.telecom;
 
+import android.app.BroadcastOptions;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.os.Bundle;
 import android.os.UserHandle;
 import android.telecom.Log;
 import android.widget.Toast;
@@ -247,8 +249,14 @@
      * Closes open system dialogs and the notification shade.
      */
     private void closeSystemDialogs(Context context) {
-        Intent intent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
-        context.sendBroadcastAsUser(intent, UserHandle.ALL);
+        Intent intent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)
+                .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        Bundle options = BroadcastOptions.makeBasic()
+                .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT)
+                .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE)
+                .toBundle();
+        context.sendBroadcastAsUser(intent, UserHandle.ALL, null /* receiverPermission */,
+                options);
     }
 
     private void sendSmsIntent(Intent intent, UserHandle userHandle) {
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index ee7aba6..99a8d3d 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.CALL_PHONE;
 import static android.Manifest.permission.CALL_PRIVILEGED;
 import static android.Manifest.permission.DUMP;
+import static android.Manifest.permission.MANAGE_OWN_CALLS;
 import static android.Manifest.permission.MODIFY_PHONE_STATE;
 import static android.Manifest.permission.READ_PHONE_NUMBERS;
 import static android.Manifest.permission.READ_PHONE_STATE;
@@ -26,7 +27,10 @@
 import static android.Manifest.permission.READ_SMS;
 import static android.Manifest.permission.REGISTER_SIM_SUBSCRIPTION;
 import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
-import static android.Manifest.permission.MANAGE_OWN_CALLS;
+import static android.telecom.CallAttributes.DIRECTION_INCOMING;
+import static android.telecom.CallAttributes.DIRECTION_OUTGOING;
+import static android.telecom.CallException.CODE_ERROR_UNKNOWN;
+import static android.telecom.TelecomManager.TELECOM_TRANSACTION_SUCCESS;
 
 import android.Manifest;
 import android.app.ActivityManager;
@@ -43,14 +47,19 @@
 import android.content.pm.PackageManager;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
+import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.OutcomeReceiver;
 import android.os.Process;
+import android.os.RemoteException;
 import android.os.UserHandle;
 import android.provider.BlockedNumberContract;
 import android.provider.Settings;
+import android.telecom.CallAttributes;
+import android.telecom.CallException;
 import android.telecom.Log;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
@@ -62,15 +71,28 @@
 import android.text.TextUtils;
 import android.util.EventLog;
 
+import androidx.annotation.NonNull;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telecom.ICallControl;
+import com.android.internal.telecom.ICallEventCallback;
 import com.android.internal.telecom.ITelecomService;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.telecom.components.UserCallIntentProcessorFactory;
 import com.android.server.telecom.settings.BlockedNumbersActivity;
+import com.android.server.telecom.voip.IncomingCallTransaction;
+import com.android.server.telecom.voip.OutgoingCallTransaction;
+import com.android.server.telecom.voip.TransactionManager;
+import com.android.server.telecom.voip.VoipCallTransaction;
+import com.android.server.telecom.voip.VoipCallTransactionResult;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
 
 // TODO: Needed for move to system service: import com.android.internal.R;
 
@@ -109,12 +131,149 @@
         }
     }
 
+    private static final String TAG = "TelecomServiceImpl";
     private static final String TIME_LINE_ARG = "timeline";
     private static final int DEFAULT_VIDEO_STATE = -1;
     private static final String PERMISSION_HANDLE_CALL_INTENT =
             "android.permission.HANDLE_CALL_INTENT";
+    private static final String ADD_CALL_ERR_MSG = "Call could not be created or found. "
+            + "Retry operation.";
+    private AnomalyReporterAdapter mAnomalyReporter = new AnomalyReporterAdapterImpl();
+
+    /**
+     * Anomaly Report UUIDs and corresponding error descriptions specific to TelecomServiceImpl.
+     */
+    public static final UUID REGISTER_PHONE_ACCOUNT_ERROR_UUID =
+            UUID.fromString("0e49f82e-6acc-48a9-b088-66c8296c1eb5");
+    public static final String REGISTER_PHONE_ACCOUNT_ERROR_MSG =
+            "Exception thrown while registering phone account.";
+    public static final UUID SET_USER_PHONE_ACCOUNT_ERROR_UUID =
+            UUID.fromString("80866066-7818-4869-bd44-1f7f689543e2");
+    public static final String SET_USER_PHONE_ACCOUNT_ERROR_MSG =
+            "Exception thrown while setting the user selected outgoing phone account.";
+    public static final UUID GET_CALL_CAPABLE_ACCOUNTS_ERROR_UUID =
+            UUID.fromString("4f39b865-01f2-4c1f-83a5-37ce52807e83");
+    public static final String GET_CALL_CAPABLE_ACCOUNTS_ERROR_MSG =
+            "Exception thrown while getting the call capable phone accounts";
+    public static final UUID GET_PHONE_ACCOUNT_ERROR_UUID =
+            UUID.fromString("b653c1f0-91b4-45c8-ad05-3ee4d1006c7f");
+    public static final String GET_PHONE_ACCOUNT_ERROR_MSG =
+            "Exception thrown while retrieving the phone account.";
+    public static final UUID GET_SIM_MANAGER_ERROR_UUID =
+            UUID.fromString("4244cb3f-bd02-4cc5-9f90-f41ea62ce0bb");
+    public static final String GET_SIM_MANAGER_ERROR_MSG =
+            "Exception thrown while retrieving the SIM CallManager.";
+    public static final UUID GET_SIM_MANAGER_FOR_USER_ERROR_UUID =
+            UUID.fromString("5d347ce7-7527-40d3-b98a-09b423ad031c");
+    public static final String GET_SIM_MANAGER_FOR_USER_ERROR_MSG =
+            "Exception thrown while retrieving the SIM CallManager based on the provided user.";
+    public static final UUID PLACE_CALL_SECURITY_EXCEPTION_ERROR_UUID =
+            UUID.fromString("4edf6c8d-1e43-4c94-b0fc-a40c8d80cfe8");
+    public static final String PLACE_CALL_SECURITY_EXCEPTION_ERROR_MSG =
+            "Security exception thrown while placing an outgoing call.";
+
+    @VisibleForTesting
+    public void setAnomalyReporterAdapter(AnomalyReporterAdapter mAnomalyReporterAdapter){
+        mAnomalyReporter = mAnomalyReporterAdapter;
+    }
 
     private final ITelecomService.Stub mBinderImpl = new ITelecomService.Stub() {
+
+        @Override
+        public void addCall(CallAttributes callAttributes, ICallEventCallback callEventCallback,
+                String callId, String callingPackage) {
+            try {
+                Log.startSession("TSI.aC", Log.getPackageAbbreviation(callingPackage));
+                Log.i(TAG, "addCall: id=[%s], attributes=[%s]", callId, callAttributes);
+                PhoneAccountHandle handle = callAttributes.getPhoneAccountHandle();
+
+                // enforce permissions and arguments
+                enforcePermission(android.Manifest.permission.MANAGE_OWN_CALLS);
+                enforceUserHandleMatchesCaller(handle);
+                enforcePhoneAccountIsNotManaged(handle);// only allow self-managed packages (temp.)
+                enforcePhoneAccountIsRegisteredEnabled(handle, handle.getUserHandle());
+                enforceCallingPackage(callingPackage, "addCall");
+
+                // add extras about info used for FGS delegation
+                Bundle extras = new Bundle();
+                extras.putInt(CallAttributes.CALLER_UID_KEY, Binder.getCallingUid());
+                extras.putInt(CallAttributes.CALLER_PID_KEY, Binder.getCallingPid());
+
+                VoipCallTransaction transaction = null;
+                // create transaction based on the call direction
+                switch (callAttributes.getDirection()) {
+                    case DIRECTION_OUTGOING:
+                        transaction = new OutgoingCallTransaction(callId, mContext, callAttributes,
+                                mCallsManager, extras);
+                        break;
+                    case DIRECTION_INCOMING:
+                        transaction = new IncomingCallTransaction(callId, callAttributes,
+                                mCallsManager, extras);
+                        break;
+                    default:
+                        throw new IllegalArgumentException(String.format("Invalid Call Direction. "
+                                        + "Was [%d] but should be within [%d,%d]",
+                                callAttributes.getDirection(), DIRECTION_INCOMING,
+                                DIRECTION_OUTGOING));
+                }
+
+                mTransactionManager.addTransaction(transaction, new OutcomeReceiver<>() {
+                    @Override
+                    public void onResult(VoipCallTransactionResult result) {
+                        Log.d(TAG, "addCall: onResult");
+                        Call call = result.getCall();
+
+                        if (call == null || !call.getId().equals(callId)) {
+                            Log.i(TAG, "addCall: onResult: call is null or id mismatch");
+                            onAddCallControl(callId, callEventCallback, null,
+                                    new CallException(ADD_CALL_ERR_MSG, CODE_ERROR_UNKNOWN));
+                            return;
+                        }
+
+                        TransactionalServiceWrapper serviceWrapper =
+                                mTransactionalServiceRepository
+                                        .addNewCallForTransactionalServiceWrapper(handle,
+                                                callEventCallback, mCallsManager, call);
+
+                        call.setTransactionServiceWrapper(serviceWrapper);
+                        ICallControl clientCallControl = serviceWrapper.getICallControl();
+
+                        if (clientCallControl == null) {
+                            throw new IllegalStateException("TransactionalServiceWrapper"
+                                    + "#ICallControl is null.");
+                        }
+
+                        // finally, send objects back to the client
+                        onAddCallControl(callId, callEventCallback, clientCallControl, null);
+                    }
+
+                    @Override
+                    public void onError(@NonNull CallException exception) {
+                        Log.d(TAG, "addCall: onError: e=[%s]", exception.toString());
+                        onAddCallControl(callId, callEventCallback, null, exception);
+                    }
+                });
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        private void onAddCallControl(String callId, ICallEventCallback callEventCallback,
+                ICallControl callControl, CallException callException) {
+            try {
+                if (callException == null) {
+                    callEventCallback.onAddCallControl(callId, TELECOM_TRANSACTION_SUCCESS,
+                            callControl, null);
+                } else {
+                    callEventCallback.onAddCallControl(callId,
+                            CallException.CODE_ERROR_UNKNOWN,
+                            null, callException);
+                }
+            } catch (RemoteException remoteException) {
+                throw remoteException.rethrowAsRuntimeException();
+            }
+        }
+
         @Override
         public PhoneAccountHandle getDefaultOutgoingPhoneAccount(String uriScheme,
                 String callingPackage, String callingFeatureId) {
@@ -134,11 +293,11 @@
                         Binder.restoreCallingIdentity(token);
                     }
                     if (isCallerSimCallManager(phoneAccountHandle)
-                        || canReadPhoneState(
+                            || canReadPhoneState(
                             callingPackage,
                             callingFeatureId,
                             "getDefaultOutgoingPhoneAccount")) {
-                      return phoneAccountHandle;
+                        return phoneAccountHandle;
                     }
                     return null;
                 }
@@ -181,6 +340,8 @@
                                 accountHandle, callingUserHandle);
                     } catch (Exception e) {
                         Log.e(this, e, "setUserSelectedOutgoingPhoneAccount");
+                        mAnomalyReporter.reportAnomaly(SET_USER_PHONE_ACCOUNT_ERROR_UUID,
+                                SET_USER_PHONE_ACCOUNT_ERROR_MSG);
                         throw e;
                     } finally {
                         Binder.restoreCallingIdentity(token);
@@ -207,13 +368,17 @@
                 }
                 synchronized (mLock) {
                     final UserHandle callingUserHandle = Binder.getCallingUserHandle();
+                    boolean crossUserAccess = hasInAppCrossUserPermission();
                     long token = Binder.clearCallingIdentity();
                     try {
                         return new ParceledListSlice<>(
                                 mPhoneAccountRegistrar.getCallCapablePhoneAccounts(null,
-                                includeDisabledAccounts, callingUserHandle));
+                                        includeDisabledAccounts, callingUserHandle,
+                                        crossUserAccess));
                     } catch (Exception e) {
                         Log.e(this, e, "getCallCapablePhoneAccounts");
+                        mAnomalyReporter.reportAnomaly(GET_CALL_CAPABLE_ACCOUNTS_ERROR_UUID,
+                                GET_CALL_CAPABLE_ACCOUNTS_ERROR_MSG);
                         throw e;
                     } finally {
                         Binder.restoreCallingIdentity(token);
@@ -258,8 +423,7 @@
                 Log.startSession("TSI.gOSMPA", Log.getPackageAbbreviation(callingPackage));
                 try {
                     enforceCallingPackage(callingPackage, "getOwnSelfManagedPhoneAccounts");
-                }
-                catch(SecurityException se){
+                } catch (SecurityException se) {
                     EventLog.writeEvent(0x534e4554, "231986341", Binder.getCallingUid(),
                             "getOwnSelfManagedPhoneAccounts: invalid calling package");
                     throw se;
@@ -273,7 +437,7 @@
                     try {
                         return new ParceledListSlice<>(mPhoneAccountRegistrar
                                 .getSelfManagedPhoneAccountsForPackage(callingPackage,
-                                callingUserHandle));
+                                        callingUserHandle));
                     } catch (Exception e) {
                         Log.e(this, e,
                                 "getSelfManagedPhoneAccountsForPackage");
@@ -306,8 +470,8 @@
                     long token = Binder.clearCallingIdentity();
                     try {
                         return new ParceledListSlice<>(mPhoneAccountRegistrar
-                                .getCallCapablePhoneAccounts(uriScheme, false,
-                                callingUserHandle));
+                            .getCallCapablePhoneAccounts(uriScheme, false,
+                                    callingUserHandle, false));
                     } catch (Exception e) {
                         Log.e(this, e, "getPhoneAccountsSupportingScheme %s", uriScheme);
                         throw e;
@@ -346,7 +510,7 @@
                 try {
                     Log.startSession("TSI.gPAFP");
                     return new ParceledListSlice<>(mPhoneAccountRegistrar
-                            .getPhoneAccountsForPackage(packageName, callingUserHandle));
+                            .getAllPhoneAccountHandlesForPackage(callingUserHandle, packageName));
                 } catch (Exception e) {
                     Log.e(this, e, "getPhoneAccountsForPackage %s", packageName);
                     throw e;
@@ -361,42 +525,51 @@
         public PhoneAccount getPhoneAccount(PhoneAccountHandle accountHandle,
                 String callingPackage) {
             try {
-                enforceCallingPackage(callingPackage, "getPhoneAccount");
-            } catch (SecurityException se) {
-                EventLog.writeEvent(0x534e4554, "196406138", Binder.getCallingUid(),
-                        "getPhoneAccount: invalid calling package");
-                throw se;
-            }
-            synchronized (mLock) {
-                final UserHandle callingUserHandle = Binder.getCallingUserHandle();
-                if (CompatChanges.isChangeEnabled(
-                        TelecomManager.ENABLE_GET_PHONE_ACCOUNT_PERMISSION_PROTECTION,
-                        callingPackage, Binder.getCallingUserHandle())) {
-                    if (Binder.getCallingUid() != Process.SHELL_UID &&
-                            !canGetPhoneAccount(callingPackage, accountHandle)) {
-                        SecurityException e = new SecurityException("getPhoneAccount API requires" +
-                                "READ_PHONE_NUMBERS");
+                Log.startSession("TSI.gPA", Log.getPackageAbbreviation(callingPackage));
+                try {
+                    enforceCallingPackage(callingPackage, "getPhoneAccount");
+                } catch (SecurityException se) {
+                    EventLog.writeEvent(0x534e4554, "196406138", Binder.getCallingUid(),
+                            "getPhoneAccount: invalid calling package");
+                    throw se;
+                }
+                synchronized (mLock) {
+                    final UserHandle callingUserHandle = Binder.getCallingUserHandle();
+                    if (CompatChanges.isChangeEnabled(
+                            TelecomManager.ENABLE_GET_PHONE_ACCOUNT_PERMISSION_PROTECTION,
+                            callingPackage, Binder.getCallingUserHandle())) {
+                        if (Binder.getCallingUid() != Process.SHELL_UID &&
+                                !canGetPhoneAccount(callingPackage, accountHandle)) {
+                            SecurityException e = new SecurityException(
+                                    "getPhoneAccount API requires" +
+                                            "READ_PHONE_NUMBERS");
+                            Log.e(this, e, "getPhoneAccount %s", accountHandle);
+                            throw e;
+                        }
+                    }
+                    Set<String> permissions = computePermissionsForBoundPackage(
+                            Set.of(MODIFY_PHONE_STATE), null);
+                    long token = Binder.clearCallingIdentity();
+                    try {
+                        // In ideal case, we should not resolve the handle across profiles. But
+                        // given the fact that profile's call is handled by its parent user's
+                        // in-call UI, parent user's in call UI need to be able to get phone account
+                        // from the profile's phone account handle.
+                        PhoneAccount account = mPhoneAccountRegistrar
+                                .getPhoneAccount(accountHandle, callingUserHandle,
+                                        /* acrossProfiles */ true);
+                        return maybeCleansePhoneAccount(account, permissions);
+                    } catch (Exception e) {
                         Log.e(this, e, "getPhoneAccount %s", accountHandle);
+                        mAnomalyReporter.reportAnomaly(GET_PHONE_ACCOUNT_ERROR_UUID,
+                                GET_PHONE_ACCOUNT_ERROR_MSG);
                         throw e;
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
                     }
                 }
-                long token = Binder.clearCallingIdentity();
-                try {
-                    Log.startSession("TSI.gPA");
-                    // In ideal case, we should not resolve the handle across profiles. But given
-                    // the fact that profile's call is handled by its parent user's in-call UI,
-                    // parent user's in call UI need to be able to get phone account from the
-                    // profile's phone account handle.
-                    return mPhoneAccountRegistrar
-                            .getPhoneAccount(accountHandle, callingUserHandle,
-                                    /* acrossProfiles */ true);
-                } catch (Exception e) {
-                    Log.e(this, e, "getPhoneAccount %s", accountHandle);
-                    throw e;
-                } finally {
-                    Binder.restoreCallingIdentity(token);
-                    Log.endSession();
-                }
+            } finally {
+                Log.endSession();
             }
         }
 
@@ -446,7 +619,7 @@
                     long token = Binder.clearCallingIdentity();
                     try {
                         return new ParceledListSlice<>(mPhoneAccountRegistrar
-                                .getAllPhoneAccounts(callingUserHandle));
+                                .getAllPhoneAccounts(callingUserHandle, false));
                     } catch (Exception e) {
                         Log.e(this, e, "getAllPhoneAccounts");
                         throw e;
@@ -474,10 +647,12 @@
 
                 synchronized (mLock) {
                     final UserHandle callingUserHandle = Binder.getCallingUserHandle();
+                    boolean crossUserAccess = hasInAppCrossUserPermission();
                     long token = Binder.clearCallingIdentity();
                     try {
                         return new ParceledListSlice<>(mPhoneAccountRegistrar
-                                .getAllPhoneAccountHandles(callingUserHandle));
+                                .getAllPhoneAccountHandles(callingUserHandle,
+                                        crossUserAccess));
                     } catch (Exception e) {
                         Log.e(this, e, "getAllPhoneAccounts");
                         throw e;
@@ -491,10 +666,10 @@
         }
 
         @Override
-        public PhoneAccountHandle getSimCallManager(int subId) {
+        public PhoneAccountHandle getSimCallManager(int subId, String callingPackage) {
             synchronized (mLock) {
                 try {
-                    Log.startSession("TSI.gSCM");
+                    Log.startSession("TSI.gSCM", Log.getPackageAbbreviation(callingPackage));
                     final int callingUid = Binder.getCallingUid();
                     final int user = UserHandle.getUserId(callingUid);
                     long token = Binder.clearCallingIdentity();
@@ -508,6 +683,8 @@
                     }
                 } catch (Exception e) {
                     Log.e(this, e, "getSimCallManager");
+                    mAnomalyReporter.reportAnomaly(GET_SIM_MANAGER_ERROR_UUID,
+                            GET_SIM_MANAGER_ERROR_MSG);
                     throw e;
                 } finally {
                     Log.endSession();
@@ -516,10 +693,10 @@
         }
 
         @Override
-        public PhoneAccountHandle getSimCallManagerForUser(int user) {
+        public PhoneAccountHandle getSimCallManagerForUser(int user, String callingPackage) {
             synchronized (mLock) {
                 try {
-                    Log.startSession("TSI.gSCMFU");
+                    Log.startSession("TSI.gSCMFU", Log.getPackageAbbreviation(callingPackage));
                     final int callingUid = Binder.getCallingUid();
                     if (user != ActivityManager.getCurrentUser()) {
                         enforceCrossUserPermission(callingUid);
@@ -532,6 +709,8 @@
                     }
                 } catch (Exception e) {
                     Log.e(this, e, "getSimCallManager");
+                    mAnomalyReporter.reportAnomaly(GET_SIM_MANAGER_FOR_USER_ERROR_UUID,
+                            GET_SIM_MANAGER_FOR_USER_ERROR_MSG);
                     throw e;
                 } finally {
                     Log.endSession();
@@ -540,9 +719,9 @@
         }
 
         @Override
-        public void registerPhoneAccount(PhoneAccount account) {
+        public void registerPhoneAccount(PhoneAccount account, String callingPackage) {
             try {
-                Log.startSession("TSI.rPA");
+                Log.startSession("TSI.rPA", Log.getPackageAbbreviation(callingPackage));
                 synchronized (mLock) {
                     try {
                         enforcePhoneAccountModificationForPackage(
@@ -573,9 +752,9 @@
                         // and carrier-designated SIM call manager can register accounts with these
                         // capabilities.
                         if (account.hasCapabilities(
-                                        PhoneAccount.CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS)
+                                PhoneAccount.CAPABILITY_SUPPORTS_VOICE_CALLING_INDICATIONS)
                                 || account.hasCapabilities(
-                                        PhoneAccount.CAPABILITY_VOICE_CALLING_AVAILABLE)) {
+                                PhoneAccount.CAPABILITY_VOICE_CALLING_AVAILABLE)) {
                             enforceRegisterVoiceCallingIndicationCapabilities(account);
                         }
                         Bundle extras = account.getExtras();
@@ -599,14 +778,21 @@
                                     .build();
                         }
 
+                        // Validate the profile boundary of the given image URI.
+                        validateAccountIconUserBoundary(account.getIcon());
+
                         final long token = Binder.clearCallingIdentity();
                         try {
+                            Log.i(this, "registerPhoneAccount: account=%s",
+                                    account);
                             mPhoneAccountRegistrar.registerPhoneAccount(account);
                         } finally {
                             Binder.restoreCallingIdentity(token);
                         }
                     } catch (Exception e) {
                         Log.e(this, e, "registerPhoneAccount %s", account);
+                        mAnomalyReporter.reportAnomaly(REGISTER_PHONE_ACCOUNT_ERROR_UUID,
+                                REGISTER_PHONE_ACCOUNT_ERROR_MSG);
                         throw e;
                     }
                 }
@@ -616,10 +802,11 @@
         }
 
         @Override
-        public void unregisterPhoneAccount(PhoneAccountHandle accountHandle) {
+        public void unregisterPhoneAccount(PhoneAccountHandle accountHandle,
+                String callingPackage) {
             synchronized (mLock) {
                 try {
-                    Log.startSession("TSI.uPA");
+                    Log.startSession("TSI.uPA", Log.getPackageAbbreviation(callingPackage));
                     enforcePhoneAccountModificationForPackage(
                             accountHandle.getComponentName().getPackageName());
                     enforceUserHandleMatchesCaller(accountHandle);
@@ -662,7 +849,7 @@
         public boolean isVoiceMailNumber(PhoneAccountHandle accountHandle, String number,
                 String callingPackage, String callingFeatureId) {
             try {
-                Log.startSession("TSI.iVMN");
+                Log.startSession("TSI.iVMN", Log.getPackageAbbreviation(callingPackage));
                 synchronized (mLock) {
                     if (!canReadPhoneState(callingPackage, callingFeatureId, "isVoiceMailNumber")) {
                         return false;
@@ -695,7 +882,7 @@
         public String getVoiceMailNumber(PhoneAccountHandle accountHandle, String callingPackage,
                 String callingFeatureId) {
             try {
-                Log.startSession("TSI.gVMN");
+                Log.startSession("TSI.gVMN", Log.getPackageAbbreviation(callingPackage));
                 if (!canReadPhoneState(callingPackage, callingFeatureId, "getVoiceMailNumber")) {
                     return null;
                 }
@@ -731,7 +918,7 @@
         public String getLine1Number(PhoneAccountHandle accountHandle, String callingPackage,
                 String callingFeatureId) {
             try {
-                Log.startSession("getL1N");
+                Log.startSession("getL1N", Log.getPackageAbbreviation(callingPackage));
                 if (!canReadPhoneNumbers(callingPackage, callingFeatureId, "getLine1Number")) {
                     return null;
                 }
@@ -768,15 +955,18 @@
         @Override
         public void silenceRinger(String callingPackage) {
             try {
-                Log.startSession("TSI.sR");
+                Log.startSession("TSI.sR", Log.getPackageAbbreviation(callingPackage));
                 synchronized (mLock) {
                     enforcePermissionOrPrivilegedDialer(MODIFY_PHONE_STATE, callingPackage);
-
+                    UserHandle callingUserHandle = Binder.getCallingUserHandle();
+                    boolean crossUserAccess = hasInAppCrossUserPermission();
                     long token = Binder.clearCallingIdentity();
                     try {
                         Log.i(this, "Silence Ringer requested by %s", callingPackage);
-                        mCallsManager.getCallAudioManager().silenceRingers();
-                        mCallsManager.getInCallController().silenceRinger();
+                        Set<UserHandle> userHandles = mCallsManager.getCallAudioManager().
+                                silenceRingers(mContext, callingUserHandle,
+                                        crossUserAccess);
+                        mCallsManager.getInCallController().silenceRinger(userHandles);
                     } finally {
                         Binder.restoreCallingIdentity(token);
                     }
@@ -789,7 +979,7 @@
         /**
          * @see android.telecom.TelecomManager#getDefaultPhoneApp
          * @deprecated - Use {@link android.telecom.TelecomManager#getDefaultDialerPackage()}
-         *         instead.
+         * instead.
          */
         @Override
         public ComponentName getDefaultPhoneApp() {
@@ -803,18 +993,19 @@
 
         /**
          * @return the package name of the current user-selected default dialer. If no default
-         *         has been selected, the package name of the system dialer is returned. If
-         *         neither exists, then {@code null} is returned.
+         * has been selected, the package name of the system dialer is returned. If
+         * neither exists, then {@code null} is returned.
          * @see android.telecom.TelecomManager#getDefaultDialerPackage
          */
         @Override
-        public String getDefaultDialerPackage() {
+        public String getDefaultDialerPackage(String callingPackage) {
             try {
-                Log.startSession("TSI.gDDP");
+                Log.startSession("TSI.gDDP", Log.getPackageAbbreviation(callingPackage));
+                int callerUserId = UserHandle.getCallingUserId();
                 final long token = Binder.clearCallingIdentity();
                 try {
                     return mDefaultDialerCache.getDefaultDialerApplication(
-                            ActivityManager.getCurrentUser());
+                            callerUserId);
                 } finally {
                     Binder.restoreCallingIdentity(token);
                 }
@@ -826,8 +1017,8 @@
         /**
          * @param userId user id to get the default dialer package for
          * @return the package name of the current user-selected default dialer. If no default
-         *         has been selected, the package name of the system dialer is returned. If
-         *         neither exists, then {@code null} is returned.
+         * has been selected, the package name of the system dialer is returned. If
+         * neither exists, then {@code null} is returned.
          * @see android.telecom.TelecomManager#getDefaultDialerPackage
          */
         @Override
@@ -852,9 +1043,9 @@
          * @see android.telecom.TelecomManager#getSystemDialerPackage
          */
         @Override
-        public String getSystemDialerPackage() {
+        public String getSystemDialerPackage(String callingPackage) {
             try {
-                Log.startSession("TSI.gSDP");
+                Log.startSession("TSI.gSDP", Log.getPackageAbbreviation(callingPackage));
                 return mDefaultDialerCache.getSystemDialerApplication();
             } finally {
                 Log.endSession();
@@ -885,13 +1076,14 @@
         @Override
         public boolean isInCall(String callingPackage, String callingFeatureId) {
             try {
-                Log.startSession("TSI.iIC");
+                Log.startSession("TSI.iIC", Log.getPackageAbbreviation(callingPackage));
                 if (!canReadPhoneState(callingPackage, callingFeatureId, "isInCall")) {
                     return false;
                 }
 
                 synchronized (mLock) {
-                    return mCallsManager.hasOngoingCalls();
+                    return mCallsManager.hasOngoingCalls(Binder.getCallingUserHandle(),
+                            hasInAppCrossUserPermission());
                 }
             } finally {
                 Log.endSession();
@@ -904,7 +1096,7 @@
         @Override
         public boolean hasManageOngoingCallsPermission(String callingPackage) {
             try {
-                Log.startSession("TSI.hMOCP");
+                Log.startSession("TSI.hMOCP", Log.getPackageAbbreviation(callingPackage));
                 enforceCallingPackage(callingPackage, "hasManageOngoingCallsPermission");
                 return PermissionChecker.checkPermissionForDataDeliveryFromDataSource(
                         mContext, Manifest.permission.MANAGE_ONGOING_CALLS,
@@ -913,7 +1105,7 @@
                                 new AttributionSource(Binder.getCallingUid(),
                                         callingPackage, /*attributionTag*/ null)),
                         "Checking whether the caller has MANAGE_ONGOING_CALLS permission")
-                                == PermissionChecker.PERMISSION_GRANTED;
+                        == PermissionChecker.PERMISSION_GRANTED;
             } finally {
                 Log.endSession();
             }
@@ -925,14 +1117,15 @@
         @Override
         public boolean isInManagedCall(String callingPackage, String callingFeatureId) {
             try {
-                Log.startSession("TSI.iIMC");
+                Log.startSession("TSI.iIMC", Log.getPackageAbbreviation(callingPackage));
                 if (!canReadPhoneState(callingPackage, callingFeatureId, "isInManagedCall")) {
                     throw new SecurityException("Only the default dialer or caller with " +
                             "READ_PHONE_STATE permission can use this method.");
                 }
 
                 synchronized (mLock) {
-                    return mCallsManager.hasOngoingManagedCalls();
+                    return mCallsManager.hasOngoingManagedCalls(Binder.getCallingUserHandle(),
+                            hasInAppCrossUserPermission());
                 }
             } finally {
                 Log.endSession();
@@ -1002,6 +1195,22 @@
         public int getCallStateUsingPackage(String callingPackage, String callingFeatureId) {
             try {
                 Log.startSession("TSI.getCallStateUsingPackage");
+
+                // ensure the callingPackage is not spoofed
+                // skip check for privileged UIDs and throw SE if package does not match records
+                if (!isPrivilegedUid(callingPackage)
+                        && !callingUidMatchesPackageManagerRecords(callingPackage)) {
+                    EventLog.writeEvent(0x534e4554, "236813210", Binder.getCallingUid(),
+                            "getCallStateUsingPackage");
+                    Log.i(this,
+                            "getCallStateUsingPackage: packageName does not match records for "
+                                    + "callingPackage=[%s], callingUid=[%d]",
+                            callingPackage, Binder.getCallingUid());
+                    throw new SecurityException(String.format("getCallStateUsingPackage: "
+                                    + "enforceCallingPackage: callingPackage=[%s], callingUid=[%d]",
+                            callingPackage, Binder.getCallingUid()));
+                }
+
                 if (CompatChanges.isChangeEnabled(
                         TelecomManager.ENABLE_GET_CALL_STATE_PERMISSION_PROTECTION, callingPackage,
                         Binder.getCallingUserHandle())) {
@@ -1020,6 +1229,19 @@
             }
         }
 
+        private boolean isPrivilegedUid(String callingPackage) {
+            int callingUid = Binder.getCallingUid();
+            boolean isPrivileged = false;
+            switch (callingUid) {
+                case Process.ROOT_UID:
+                case Process.SYSTEM_UID:
+                case Process.SHELL_UID:
+                    isPrivileged = true;
+                    break;
+            }
+            return isPrivileged;
+        }
+
         /**
          * @see android.telecom.TelecomManager#endCall
          */
@@ -1068,7 +1290,6 @@
 
         /**
          * @see android.telecom.TelecomManager#acceptRingingCall(int)
-         *
          */
         @Override
         public void acceptRingingCallWithVideoState(String packageName, int videoState) {
@@ -1103,9 +1324,10 @@
 
                 synchronized (mLock) {
 
+                    UserHandle callingUser = Binder.getCallingUserHandle();
                     long token = Binder.clearCallingIdentity();
                     try {
-                        mCallsManager.getInCallController().bringToForeground(showDialpad);
+                        mCallsManager.getInCallController().bringToForeground(showDialpad, callingUser);
                     } finally {
                         Binder.restoreCallingIdentity(token);
                     }
@@ -1136,6 +1358,7 @@
                 Log.endSession();
             }
         }
+
         /**
          * @see android.telecom.TelecomManager#handleMmi
          */
@@ -1158,7 +1381,7 @@
                 }
 
                 return retval;
-            }finally {
+            } finally {
                 Log.endSession();
             }
         }
@@ -1199,7 +1422,7 @@
                     Binder.restoreCallingIdentity(token);
                 }
                 return retval;
-            }finally {
+            } finally {
                 Log.endSession();
             }
         }
@@ -1282,9 +1505,10 @@
          * @see android.telecom.TelecomManager#addNewIncomingCall
          */
         @Override
-        public void addNewIncomingCall(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
+        public void addNewIncomingCall(PhoneAccountHandle phoneAccountHandle, Bundle extras,
+                String callingPackage) {
             try {
-                Log.startSession("TSI.aNIC");
+                Log.startSession("TSI.aNIC", Log.getPackageAbbreviation(callingPackage));
                 synchronized (mLock) {
                     Log.i(this, "Adding new incoming call with phoneAccountHandle %s",
                             phoneAccountHandle);
@@ -1292,7 +1516,7 @@
                             phoneAccountHandle.getComponentName() != null) {
                         if (isCallerSimCallManager(phoneAccountHandle)
                                 && TelephonyUtil.isPstnComponentName(
-                                        phoneAccountHandle.getComponentName())) {
+                                phoneAccountHandle.getComponentName())) {
                             Log.v(this, "Allowing call manager to add incoming call with PSTN" +
                                     " handle");
                         } else {
@@ -1302,7 +1526,7 @@
                             // Make sure it doesn't cross the UserHandle boundary
                             enforceUserHandleMatchesCaller(phoneAccountHandle);
                             enforcePhoneAccountIsRegisteredEnabled(phoneAccountHandle,
-                                    Binder.getCallingUserHandle());
+                                    phoneAccountHandle.getUserHandle());
                             if (isSelfManagedConnectionService(phoneAccountHandle)) {
                                 // Self-managed phone account, ensure it has MANAGE_OWN_CALLS.
                                 mContext.enforceCallingOrSelfPermission(
@@ -1344,9 +1568,10 @@
          * @see android.telecom.TelecomManager#addNewIncomingConference
          */
         @Override
-        public void addNewIncomingConference(PhoneAccountHandle phoneAccountHandle, Bundle extras) {
+        public void addNewIncomingConference(PhoneAccountHandle phoneAccountHandle, Bundle extras,
+                String callingPackage) {
             try {
-                Log.startSession("TSI.aNIC");
+                Log.startSession("TSI.aNIC", Log.getPackageAbbreviation(callingPackage));
                 synchronized (mLock) {
                     Log.i(this, "Adding new incoming conference with phoneAccountHandle %s",
                             phoneAccountHandle);
@@ -1354,7 +1579,7 @@
                             phoneAccountHandle.getComponentName() != null) {
                         if (isCallerSimCallManager(phoneAccountHandle)
                                 && TelephonyUtil.isPstnComponentName(
-                                        phoneAccountHandle.getComponentName())) {
+                                phoneAccountHandle.getComponentName())) {
                             Log.v(this, "Allowing call manager to add incoming conference" +
                                     " with PSTN handle");
                         } else {
@@ -1366,8 +1591,9 @@
                             enforcePhoneAccountIsRegisteredEnabled(phoneAccountHandle,
                                     Binder.getCallingUserHandle());
                             if (isSelfManagedConnectionService(phoneAccountHandle)) {
-                                throw new SecurityException("Self-Managed ConnectionServices cannot add "
-                                        + "adhoc conference calls");
+                                throw new SecurityException(
+                                        "Self-Managed ConnectionServices cannot add "
+                                                + "adhoc conference calls");
                             }
                         }
                         long token = Binder.clearCallingIdentity();
@@ -1392,9 +1618,10 @@
          * @see android.telecom.TelecomManager#acceptHandover
          */
         @Override
-        public void acceptHandover(Uri srcAddr, int videoState, PhoneAccountHandle destAcct) {
+        public void acceptHandover(Uri srcAddr, int videoState, PhoneAccountHandle destAcct,
+                String callingPackage) {
             try {
-                Log.startSession("TSI.aHO");
+                Log.startSession("TSI.aHO", Log.getPackageAbbreviation(callingPackage));
                 synchronized (mLock) {
                     Log.i(this, "acceptHandover; srcAddr=%s, videoState=%s, dest=%s",
                             Log.pii(srcAddr), VideoProfile.videoStateToString(videoState),
@@ -1475,7 +1702,8 @@
                             intent.putExtra(CallIntentProcessor.KEY_IS_UNKNOWN_CALL, true);
                             intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
                                     phoneAccountHandle);
-                            mCallIntentProcessorAdapter.processUnknownCallIntent(mCallsManager, intent);
+                            mCallIntentProcessorAdapter.processUnknownCallIntent(mCallsManager,
+                                    intent);
                         } finally {
                             Binder.restoreCallingIdentity(token);
                         }
@@ -1502,8 +1730,14 @@
                     throw new SecurityException("Package " + callingPackage + " is not allowed"
                             + " to start conference call");
                 }
-                mCallsManager.startConference(participants, extras, callingPackage,
-                        Binder.getCallingUserHandle());
+
+                long token = Binder.clearCallingIdentity();
+                try {
+                    mCallsManager.startConference(participants, extras, callingPackage,
+                            Binder.getCallingUserHandle());
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
             } finally {
                 Log.endSession();
             }
@@ -1520,7 +1754,6 @@
                 enforceCallingPackage(callingPackage, "placeCall");
 
                 PhoneAccountHandle phoneAccountHandle = null;
-                boolean clearPhoneAccountHandleExtra = false;
                 if (extras != null) {
                     phoneAccountHandle = extras.getParcelable(
                             TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
@@ -1529,31 +1762,42 @@
                         extras.remove(TelecomManager.EXTRA_IS_HANDOVER);
                     }
                 }
-                boolean isSelfManaged = phoneAccountHandle != null &&
+                ComponentName phoneAccountComponentName = phoneAccountHandle != null
+                        ? phoneAccountHandle.getComponentName() : null;
+                String phoneAccountPackageName = phoneAccountComponentName != null
+                        ? phoneAccountComponentName.getPackageName() : null;
+                boolean isCallerOwnerOfPhoneAccount =
+                        callingPackage.equals(phoneAccountPackageName);
+                boolean isSelfManagedPhoneAccount =
                         isSelfManagedConnectionService(phoneAccountHandle);
-                if (isSelfManaged) {
-                    try {
-                        mContext.enforceCallingOrSelfPermission(
-                                Manifest.permission.MANAGE_OWN_CALLS,
-                                "Self-managed ConnectionServices require "
-                                        + "MANAGE_OWN_CALLS permission.");
-                    } catch (SecurityException e) {
-                        // Fallback to use mobile network to avoid disclosing phone account handle
-                        // package information
-                        clearPhoneAccountHandleExtra = true;
-                    }
+                // Ensure the app's calling package matches the PhoneAccount package name before
+                // checking self-managed status so that we do not leak installed package
+                // information.
+                boolean isSelfManagedRequest = isCallerOwnerOfPhoneAccount &&
+                        isSelfManagedPhoneAccount;
+                if (isSelfManagedRequest) {
+                    // The package name of the caller matches the package name of the
+                    // PhoneAccountHandle, so ensure the app has MANAGE_OWN_CALLS permission if
+                    // self-managed.
+                    mContext.enforceCallingOrSelfPermission(
+                            Manifest.permission.MANAGE_OWN_CALLS,
+                            "Self-managed ConnectionServices require MANAGE_OWN_CALLS permission.");
+                } else if (!canCallPhone(callingPackage, callingFeatureId,
+                        "CALL_PHONE permission required to place calls.")) {
+                    // not self-managed, so CALL_PHONE is required.
+                    mAnomalyReporter.reportAnomaly(PLACE_CALL_SECURITY_EXCEPTION_ERROR_UUID,
+                            PLACE_CALL_SECURITY_EXCEPTION_ERROR_MSG);
+                    throw new SecurityException(
+                            "CALL_PHONE permission required to place calls.");
+                }
 
-                    if (!clearPhoneAccountHandleExtra && !callingPackage.equals(
-                            phoneAccountHandle.getComponentName().getPackageName())
-                            && !canCallPhone(callingPackage, callingFeatureId,
-                            "CALL_PHONE permission required to place calls.")) {
-                        // The caller is not allowed to place calls, so fallback to use mobile
-                        // network.
-                        clearPhoneAccountHandleExtra = true;
-                    }
-                } else if (!canCallPhone(callingPackage, callingFeatureId, "placeCall")) {
-                    throw new SecurityException("Package " + callingPackage
-                            + " is not allowed to place phone calls");
+                // An application can not place a call with a self-managed PhoneAccount that
+                // they do not own. If this is the case (and the app has CALL_PHONE permission),
+                // remove the PhoneAccount from the request and place the call as if it was a
+                // managed call request with no PhoneAccount specified.
+                if (!isCallerOwnerOfPhoneAccount && isSelfManagedPhoneAccount) {
+                    // extras can not be null if isSelfManagedPhoneAccount is true
+                    extras.remove(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
                 }
 
                 // Note: we can still get here for the default/system dialer, even if the Phone
@@ -1584,16 +1828,12 @@
                         final Intent intent = new Intent(hasCallPrivilegedPermission ?
                                 Intent.ACTION_CALL_PRIVILEGED : Intent.ACTION_CALL, handle);
                         if (extras != null) {
-                            if (clearPhoneAccountHandleExtra) {
-                                extras.remove(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
-                            }
                             extras.setDefusable(true);
                             intent.putExtras(extras);
                         }
                         mUserCallIntentProcessorFactory.create(mContext, userHandle)
-                                .processIntent(
-                                        intent, callingPackage, isSelfManaged ||
-                                                (hasCallAppOp && hasCallPermission),
+                                .processIntent(intent, callingPackage, isSelfManagedRequest,
+                                        (hasCallAppOp && hasCallPermission),
                                         true /* isLocalInvocation */);
                     } finally {
                         Binder.restoreCallingIdentity(token);
@@ -1633,10 +1873,11 @@
                 enforcePermission(MODIFY_PHONE_STATE);
                 enforcePermission(WRITE_SECURE_SETTINGS);
                 synchronized (mLock) {
+                    int callerUserId = UserHandle.getCallingUserId();
                     long token = Binder.clearCallingIdentity();
                     try {
                         return mDefaultDialerCache.setDefaultDialer(packageName,
-                                ActivityManager.getCurrentUser());
+                                callerUserId);
                     } finally {
                         Binder.restoreCallingIdentity(token);
                     }
@@ -1680,11 +1921,12 @@
         }
 
         /**
-         * Dumps the current state of the TelecomService.  Used when generating problem reports.
+         * Dumps the current state of the TelecomService.  Used when generating problem
+         * reports.
          *
-         * @param fd The file descriptor.
+         * @param fd     The file descriptor.
          * @param writer The print writer to dump the state to.
-         * @param args Optional dump arguments.
+         * @param args   Optional dump arguments.
          */
         @Override
         protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
@@ -1698,19 +1940,21 @@
             }
 
 
-            if (args.length > 0 && Analytics.ANALYTICS_DUMPSYS_ARG.equals(args[0])) {
+            if (args != null && args.length > 0 && Analytics.ANALYTICS_DUMPSYS_ARG.equals(
+                    args[0])) {
                 Binder.withCleanCallingIdentity(() ->
                         Analytics.dumpToEncodedProto(mContext, writer, args));
                 return;
             }
 
-            boolean isTimeLineView = (args.length > 0 && TIME_LINE_ARG.equalsIgnoreCase(args[0]));
+            boolean isTimeLineView =
+                    (args != null && args.length > 0 && TIME_LINE_ARG.equalsIgnoreCase(args[0]));
 
             final IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
             if (mCallsManager != null) {
                 pw.println("CallsManager: ");
                 pw.increaseIndent();
-                mCallsManager.dump(pw);
+                mCallsManager.dump(pw, args);
                 pw.decreaseIndent();
 
                 pw.println("PhoneAccountRegistrar: ");
@@ -1734,8 +1978,13 @@
          * @see android.telecom.TelecomManager#createManageBlockedNumbersIntent
          */
         @Override
-        public Intent createManageBlockedNumbersIntent() {
-            return BlockedNumbersActivity.getIntentForStartingActivity();
+        public Intent createManageBlockedNumbersIntent(String callingPackage) {
+            try {
+                Log.startSession("TSI.cMBNI", Log.getPackageAbbreviation(callingPackage));
+                return BlockedNumbersActivity.getIntentForStartingActivity();
+            } finally {
+                Log.endSession();
+            }
         }
 
 
@@ -1762,7 +2011,7 @@
         @Override
         public boolean isIncomingCallPermitted(PhoneAccountHandle phoneAccountHandle,
                 String callingPackage) {
-            Log.startSession("TSI.iICP");
+            Log.startSession("TSI.iICP", Log.getPackageAbbreviation(callingPackage));
             try {
                 enforceCallingPackage(callingPackage, "isIncomingCallPermitted");
                 enforcePhoneAccountHandleMatchesCaller(phoneAccountHandle, callingPackage);
@@ -1787,7 +2036,7 @@
         @Override
         public boolean isOutgoingCallPermitted(PhoneAccountHandle phoneAccountHandle,
                 String callingPackage) {
-            Log.startSession("TSI.iOCP");
+            Log.startSession("TSI.iOCP", Log.getPackageAbbreviation(callingPackage));
             try {
                 enforceCallingPackage(callingPackage, "isOutgoingCallPermitted");
                 enforcePhoneAccountHandleMatchesCaller(phoneAccountHandle, callingPackage);
@@ -1900,9 +2149,12 @@
 
         /**
          * A method intended for use in testing to clean up any calls that get stuck in the
-         * {@link CallState#DISCONNECTED} or {@link CallState#DISCONNECTING} states. Stuck calls
-         * during CTS cause cascading failures, so if the CTS test detects such a state, it should
-         * call this method via a shell command to clean up before moving on to the next test.
+         * {@link CallState#DISCONNECTED} or {@link CallState#DISCONNECTING} states. Stuck
+         * calls
+         * during CTS cause cascading failures, so if the CTS test detects such a state, it
+         * should
+         * call this method via a shell command to clean up before moving on to the next
+         * test.
          * Also cleans up any pending futures related to
          * {@link android.telecom.CallDiagnosticService}s.
          */
@@ -1913,14 +2165,18 @@
                 synchronized (mLock) {
                     enforceShellOnly(Binder.getCallingUid(), "cleanupStuckCalls");
                     Binder.withCleanCallingIdentity(() -> {
+                        Set<UserHandle> userHandles = new HashSet<>();
                         for (Call call : mCallsManager.getCalls()) {
                             call.cleanup();
                             if (call.getState() == CallState.DISCONNECTED
                                     || call.getState() == CallState.DISCONNECTING) {
                                 mCallsManager.markCallAsRemoved(call);
                             }
+                            userHandles.add(call.getUserHandleFromTargetPhoneAccount());
                         }
-                        mCallsManager.getInCallController().unbindFromServices();
+                        for (UserHandle userHandle : userHandles) {
+                            mCallsManager.getInCallController().unbindFromServices(userHandle);
+                        }
                     });
                 }
             } finally {
@@ -1930,7 +2186,8 @@
 
         /**
          * A method intended for test to clean up orphan {@link PhoneAccount}. An orphan
-         * {@link PhoneAccount} is a phone account belongs to an invalid {@link UserHandle} or a
+         * {@link PhoneAccount} is a phone account belongs to an invalid {@link UserHandle}
+         * or a
          * deleted package.
          *
          * @return the number of orphan {@code PhoneAccount} deleted.
@@ -2124,8 +2381,8 @@
          * Determines whether there are any ongoing {@link PhoneAccount#CAPABILITY_SELF_MANAGED}
          * calls for a given {@code packageName} and {@code userHandle}.
          *
-         * @param packageName the package name of the app to check calls for.
-         * @param userHandle the user handle on which to check for calls.
+         * @param packageName    the package name of the app to check calls for.
+         * @param userHandle     the user handle on which to check for calls.
          * @param callingPackage The caller's package name.
          * @return {@code true} if there are ongoing calls, {@code false} otherwise.
          */
@@ -2154,6 +2411,20 @@
         }
     };
 
+    private boolean enforceCallStreamingPermission(String packageName, PhoneAccountHandle handle,
+            int uid) {
+        // TODO: implement this permission check (make sure the calling package is the d2di package)
+        PhoneAccount account = mPhoneAccountRegistrar.getPhoneAccount(handle,
+                UserHandle.getUserHandleForUid(uid));
+        if (account == null
+                || !account.hasCapabilities(PhoneAccount.CAPABILITY_SUPPORTS_CALL_STREAMING)) {
+            throw new SecurityException(
+                    "The phone account handle in requesting can't support call streaming: "
+                            + handle);
+        }
+        return true;
+    }
+
     /**
      * @return whether to return early without doing the action/throwing
      * @throws SecurityException same as {@link Context#enforceCallingOrSelfPermission}
@@ -2204,6 +2475,8 @@
     private final SubscriptionManagerAdapter mSubscriptionManagerAdapter;
     private final SettingsSecureAdapter mSettingsSecureAdapter;
     private final TelecomSystem.SyncRoot mLock;
+    private TransactionManager mTransactionManager;
+    private final TransactionalServiceRepository mTransactionalServiceRepository;
 
     public TelecomServiceImpl(
             Context context,
@@ -2240,6 +2513,14 @@
                             defaultDialer);
             mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
         });
+
+        mTransactionManager = TransactionManager.getInstance();
+        mTransactionalServiceRepository = new TransactionalServiceRepository();
+    }
+
+    @VisibleForTesting
+    public void setTransactionManager(TransactionManager transactionManager){
+        mTransactionManager = transactionManager;
     }
 
     public ITelecomService.Stub getBinder() {
@@ -2352,6 +2633,29 @@
         }
     }
 
+    // Enforce that the PhoneAccountHandle is tied to a self-managed package and not managed (aka
+    // sim calling, etc.)
+    private void enforcePhoneAccountIsNotManaged(PhoneAccountHandle phoneAccountHandle) {
+        PhoneAccount phoneAccount = mPhoneAccountRegistrar.getPhoneAccount(phoneAccountHandle,
+                phoneAccountHandle.getUserHandle());
+        if (phoneAccount == null) {
+            throw new IllegalArgumentException("enforcePhoneAccountIsNotManaged:"
+                    + " phoneAccount is null");
+        }
+        if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
+            throw new IllegalArgumentException("enforcePhoneAccountIsNotManaged:"
+                    + " CAPABILITY_SIM_SUBSCRIPTION is not allowed");
+        }
+        if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)) {
+            throw new IllegalArgumentException("enforcePhoneAccountIsNotManaged:"
+                    + " CAPABILITY_CALL_PROVIDER is not allowed");
+        }
+        if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER)) {
+            throw new IllegalArgumentException("enforcePhoneAccountIsNotManaged:"
+                    + " CAPABILITY_CONNECTION_MANAGER is not allowed");
+        }
+    }
+
     private void enforcePhoneAccountModificationForPackage(String packageName) {
         // TODO: Use a new telecomm permission for this instead of reusing modify.
 
@@ -2383,21 +2687,89 @@
     }
 
     private void enforceCallingPackage(String packageName, String message) {
+        int callingUid = Binder.getCallingUid();
+
+        if (callingUid != Process.ROOT_UID &&
+                !callingUidMatchesPackageManagerRecords(packageName)) {
+            throw new SecurityException(message + ": Package " + packageName
+                    + " does not belong to " + callingUid);
+        }
+    }
+
+    /**
+     * helper method that compares the binder_uid to what the packageManager_uid reports for the
+     * passed in packageName.
+     *
+     * returns true if the binder_uid matches the packageManager_uid records
+     */
+    private boolean callingUidMatchesPackageManagerRecords(String packageName) {
         int packageUid = -1;
         int callingUid = Binder.getCallingUid();
-        PackageManager pm = mContext.createContextAsUser(
-            UserHandle.getUserHandleForUid(callingUid), 0).getPackageManager();
+        PackageManager pm;
+        try{
+            pm = mContext.createContextAsUser(
+                    UserHandle.getUserHandleForUid(callingUid), 0).getPackageManager();
+        }
+        catch (Exception e){
+            Log.i(this, "callingUidMatchesPackageManagerRecords:"
+                            + " createContextAsUser hit exception=[%s]", e.toString());
+            return false;
+        }
         if (pm != null) {
             try {
                 packageUid = pm.getPackageUid(packageName, 0);
             } catch (PackageManager.NameNotFoundException e) {
-                // packageUid is -1
+                // packageUid is -1.
             }
         }
-        if (packageUid != callingUid && callingUid != Process.ROOT_UID) {
-            throw new SecurityException(message + ": Package " + packageName
-                + " does not belong to " + callingUid);
+
+        if (packageUid != callingUid) {
+            Log.i(this, "callingUidMatchesPackageManagerRecords: uid mismatch found for"
+                            + "packageName=[%s]. packageManager reports packageUid=[%d] but "
+                    + "binder reports callingUid=[%d]", packageName, packageUid, callingUid);
         }
+
+        return packageUid == callingUid;
+    }
+
+    /**
+     * Note: This method should be called BEFORE clearing the binder identity.
+     *
+     * @param permissionsToValidate      set of permissions that should be checked
+     * @param alreadyComputedPermissions a list of permissions that were already checked
+     * @return all the permissions that
+     */
+    private Set<String> computePermissionsForBoundPackage(
+            Set<String> permissionsToValidate,
+            Set<String> alreadyComputedPermissions) {
+        Set<String> permissions = Objects.requireNonNullElseGet(alreadyComputedPermissions,
+                HashSet::new);
+        for (String permission : permissionsToValidate) {
+            if (mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED) {
+                permissions.add(permission);
+            }
+        }
+        return permissions;
+    }
+
+    /**
+     * This method should be used to clear {@link PhoneAccount} properties based on a
+     * callingPackages permissions.
+     *
+     * @param account     to clear properties from
+     * @param permissions the list of permissions the callingPackge has
+     * @return the account that callingPackage will receive
+     */
+    private PhoneAccount maybeCleansePhoneAccount(PhoneAccount account,
+            Set<String> permissions) {
+        if (account == null) {
+            return null;
+        }
+        PhoneAccount.Builder accountBuilder = new PhoneAccount.Builder(account);
+        if (!permissions.contains(MODIFY_PHONE_STATE)) {
+            accountBuilder.setGroupId("***");
+        }
+        return accountBuilder.build();
     }
 
     private void enforceTelecomFeature() {
@@ -2462,7 +2834,9 @@
 
     private void enforceUserHandleMatchesCaller(PhoneAccountHandle accountHandle) {
         if (!Binder.getCallingUserHandle().equals(accountHandle.getUserHandle())) {
-            throw new SecurityException("Calling UserHandle does not match PhoneAccountHandle's");
+            // Enforce INTERACT_ACROSS_USERS if the calling user handle does not match
+            // phone account's user handle
+            enforceInAppCrossUserPermission();
         }
     }
 
@@ -2481,6 +2855,18 @@
         }
     }
 
+    private void enforceInAppCrossUserPermission() {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.INTERACT_ACROSS_USERS, "Must be system or have"
+                        + " INTERACT_ACROSS_USERS permission");
+    }
+
+    private boolean hasInAppCrossUserPermission() {
+        return mContext.checkCallingOrSelfPermission(
+                Manifest.permission.INTERACT_ACROSS_USERS)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
     // to be used for TestApi methods that can only be called with SHELL UID.
     private void enforceShellOnly(int callingUid, String message) {
         if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) {
@@ -2752,4 +3138,22 @@
             mContext.sendBroadcast(intent);
         }
     }
+
+    private void validateAccountIconUserBoundary(Icon icon) {
+        // Refer to Icon#getUriString for context. The URI string is invalid for icons of
+        // incompatible types.
+        if (icon != null && (icon.getType() == Icon.TYPE_URI
+                || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) {
+            String encodedUser = icon.getUri().getEncodedUserInfo();
+            // If there is no encoded user, the URI is calling into the calling user space
+            if (encodedUser != null) {
+                int userId = Integer.parseInt(encodedUser);
+                if (userId != UserHandle.getUserId(Binder.getCallingUid())) {
+                    // If we are transcending the profile boundary, throw an error.
+                    throw new IllegalArgumentException("Attempting to register a phone account with"
+                            + " an image icon belonging to another user.");
+                }
+            }
+        }
+    }
 }
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index 237f039..8477d49 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -16,43 +16,49 @@
 
 package com.android.server.telecom;
 
+import android.Manifest;
+import android.app.ActivityManager;
+import android.bluetooth.BluetoothManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.BugreportManager;
+import android.os.DropBoxManager;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.telecom.Log;
+import android.telecom.PhoneAccountHandle;
+import android.telephony.AnomalyReporter;
+import android.telephony.TelephonyManager;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.telecom.CallAudioManager.AudioServiceFactory;
+import com.android.server.telecom.DefaultDialerCache.DefaultDialerManagerAdapter;
 import com.android.server.telecom.bluetooth.BluetoothDeviceManager;
 import com.android.server.telecom.bluetooth.BluetoothRouteManager;
 import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
+import com.android.server.telecom.callfiltering.BlockedNumbersAdapter;
 import com.android.server.telecom.components.UserCallIntentProcessor;
 import com.android.server.telecom.components.UserCallIntentProcessorFactory;
 import com.android.server.telecom.ui.AudioProcessingNotification;
 import com.android.server.telecom.ui.DisconnectedCallNotifier;
 import com.android.server.telecom.ui.IncomingCallNotifier;
 import com.android.server.telecom.ui.MissedCallNotifierImpl.MissedCallNotifierImplFactory;
-import com.android.server.telecom.CallAudioManager.AudioServiceFactory;
-import com.android.server.telecom.DefaultDialerCache.DefaultDialerManagerAdapter;
 import com.android.server.telecom.ui.ToastFactory;
-
-import android.app.ActivityManager;
-import android.bluetooth.BluetoothManager;
-import android.Manifest;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.ServiceConnection;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.net.Uri;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.telecom.Log;
-import android.telecom.PhoneAccountHandle;
-import android.widget.Toast;
-
-import androidx.annotation.NonNull;
+import com.android.server.telecom.voip.TransactionManager;
 
 import java.io.FileNotFoundException;
 import java.io.InputStream;
 import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
 
 /**
  * Top-level Application class for Telecom.
@@ -208,9 +214,14 @@
             ClockProxy clockProxy,
             RoleManagerAdapter roleManagerAdapter,
             ContactsAsyncHelper.Factory contactsAsyncHelperFactory,
-            DeviceIdleControllerAdapter deviceIdleControllerAdapter) {
+            DeviceIdleControllerAdapter deviceIdleControllerAdapter,
+            Ringer.AccessibilityManagerAdapter accessibilityManagerAdapter,
+            Executor asyncTaskExecutor,
+            BlockedNumbersAdapter blockedNumbersAdapter) {
         mContext = context.getApplicationContext();
         LogUtils.initLogging(mContext);
+        android.telecom.Log.setLock(mLock);
+        AnomalyReporter.initialize(mContext);
         DefaultDialerManagerAdapter defaultDialerAdapter =
                 new DefaultDialerCache.DefaultDialerManagerAdapterImpl();
 
@@ -269,6 +280,15 @@
                 }
             };
 
+            CallEndpointControllerFactory callEndpointControllerFactory =
+                    new CallEndpointControllerFactory() {
+                @Override
+                public CallEndpointController create(Context context, SyncRoot lock,
+                        CallsManager callsManager) {
+                    return new CallEndpointController(context, callsManager);
+                }
+            };
+
             CallDiagnosticServiceController callDiagnosticServiceController =
                     new CallDiagnosticServiceController(
                             new CallDiagnosticServiceController.ContextProxy() {
@@ -319,6 +339,17 @@
                 }
             };
 
+            EmergencyCallDiagnosticLogger emergencyCallDiagnosticLogger =
+                    new EmergencyCallDiagnosticLogger(mContext.getSystemService(
+                            TelephonyManager.class), mContext.getSystemService(
+                            BugreportManager.class), timeoutsAdapter, mContext.getSystemService(
+                            DropBoxManager.class), asyncTaskExecutor, clockProxy);
+
+            CallAnomalyWatchdog callAnomalyWatchdog = new CallAnomalyWatchdog(
+                    Executors.newSingleThreadScheduledExecutor(),
+                    mLock, timeoutsAdapter, clockProxy, emergencyCallDiagnosticLogger);
+
+            TransactionManager transactionManager = TransactionManager.getInstance();
             mCallsManager = new CallsManager(
                     mContext,
                     mLock,
@@ -348,7 +379,14 @@
                     inCallControllerFactory,
                     callDiagnosticServiceController,
                     roleManagerAdapter,
-                    toastFactory);
+                    toastFactory,
+                    callEndpointControllerFactory,
+                    callAnomalyWatchdog,
+                    accessibilityManagerAdapter,
+                    asyncTaskExecutor,
+                    blockedNumbersAdapter,
+                    transactionManager,
+                    emergencyCallDiagnosticLogger);
 
             mIncomingCallNotifier = incomingCallNotifier;
             incomingCallNotifier.setCallsManagerProxy(new IncomingCallNotifier.CallsManagerProxy() {
diff --git a/src/com/android/server/telecom/Timeouts.java b/src/com/android/server/telecom/Timeouts.java
index 36caa25..c5fdd4c 100644
--- a/src/com/android/server/telecom/Timeouts.java
+++ b/src/com/android/server/telecom/Timeouts.java
@@ -20,8 +20,8 @@
 import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.telecom.CallDiagnosticService;
-import android.telecom.CallRedirectionService;
 import android.telecom.CallDiagnostics;
+import android.telecom.CallRedirectionService;
 import android.telephony.ims.ImsReasonInfo;
 
 import java.util.concurrent.TimeUnit;
@@ -78,13 +78,135 @@
         }
 
         public long getCallStartAppOpDebounceIntervalMillis() {
-            return  Timeouts.getCallStartAppOpDebounceIntervalMillis();
+            return Timeouts.getCallStartAppOpDebounceIntervalMillis();
+        }
+
+        public long getVoipCallTransitoryStateTimeoutMillis() {
+            return Timeouts.getVoipCallTransitoryStateTimeoutMillis();
+        }
+
+        public long getVoipEmergencyCallTransitoryStateTimeoutMillis() {
+            return Timeouts.getVoipEmergencyCallTransitoryStateTimeoutMillis();
+        }
+
+        public long getNonVoipCallTransitoryStateTimeoutMillis() {
+            return Timeouts.getNonVoipCallTransitoryStateTimeoutMillis();
+        }
+
+        public long getNonVoipEmergencyCallTransitoryStateTimeoutMillis() {
+            return Timeouts.getNonVoipEmergencyCallTransitoryStateTimeoutMillis();
+        }
+
+        public long getVoipCallIntermediateStateTimeoutMillis() {
+            return Timeouts.getVoipCallIntermediateStateTimeoutMillis();
+        }
+
+        public long getVoipEmergencyCallIntermediateStateTimeoutMillis() {
+            return Timeouts.getVoipEmergencyCallIntermediateStateTimeoutMillis();
+        }
+
+        public long getNonVoipCallIntermediateStateTimeoutMillis() {
+            return Timeouts.getNonVoipCallIntermediateStateTimeoutMillis();
+        }
+
+        public long getNonVoipEmergencyCallIntermediateStateTimeoutMillis() {
+            return Timeouts.getNonVoipEmergencyCallIntermediateStateTimeoutMillis();
+        }
+
+        public long getEmergencyCallTimeBeforeUserDisconnectThresholdMillis(){
+            return Timeouts.getEmergencyCallTimeBeforeUserDisconnectThresholdMillis();
+        }
+
+        public long getEmergencyCallActiveTimeThresholdMillis(){
+            return Timeouts.getEmergencyCallActiveTimeThresholdMillis();
+        }
+
+        public int getDaysBackToSearchEmergencyDiagnosticEntries(){
+            return Timeouts.getDaysBackToSearchEmergencyDiagnosticEntries();
+
         }
     }
 
     /** A prefix to use for all keys so to not clobber the global namespace. */
     private static final String PREFIX = "telecom.";
 
+    /**
+     * threshold used to filter out ecalls that the user may have dialed by mistake
+     * It is used only when the disconnect cause is LOCAL by EmergencyDiagnosticLogger
+     */
+    private static final String EMERGENCY_CALL_TIME_BEFORE_USER_DISCONNECT_THRESHOLD_MILLIS =
+            "emergency_call_time_before_user_disconnect_threshold_millis";
+
+    /**
+     * Returns the threshold used to detect ecalls that transition to active but only for a very
+     * short duration. These short duration active calls can result in Diagnostic data collection.
+     */
+    private static final String EMERGENCY_CALL_ACTIVE_TIME_THRESHOLD_MILLIS =
+            "emergency_call_active_time_threshold_millis";
+
+    /**
+     * Time in Days that is used to filter out old dropbox entries for emergency call diagnostic
+     * data. Entries older than this are ignored
+     */
+    private static final String DAYS_BACK_TO_SEARCH_EMERGENCY_DROP_BOX_ENTRIES =
+            "days_back_to_search_emergency_drop_box_entries";
+
+    /**
+     * A prefix to use for {@link DeviceConfig} for the transitory state timeout of
+     * VoIP Call, in millis.
+     */
+    private static final String TRANSITORY_STATE_VOIP_NORMAL_TIMEOUT_MILLIS =
+            "transitory_state_voip_normal_timeout_millis";
+
+    /**
+     * A prefix to use for {@link DeviceConfig} for the transitory state timeout of
+     * VoIP emergency Call, in millis.
+     */
+    private static final String TRANSITORY_STATE_VOIP_EMERGENCY_TIMEOUT_MILLIS =
+            "transitory_state_voip_emergency_timeout_millis";
+
+    /**
+     * A prefix to use for {@link DeviceConfig} for the transitory state timeout of
+     * non-VoIP call, in millis.
+     */
+    private static final String TRANSITORY_STATE_NON_VOIP_NORMAL_TIMEOUT_MILLIS =
+            "transitory_state_non_voip_normal_timeout_millis";
+
+    /**
+     * A prefix to use for {@link DeviceConfig} for the transitory state timeout of
+     * non-VoIP emergency call, in millis.
+     */
+    private static final String TRANSITORY_STATE_NON_VOIP_EMERGENCY_TIMEOUT_MILLIS =
+            "transitory_state_non_voip_emergency_timeout_millis";
+
+    /**
+     * A prefix to use for {@link DeviceConfig} for the intermediate state timeout of
+     * VoIP call, in millis.
+     */
+    private static final String INTERMEDIATE_STATE_VOIP_NORMAL_TIMEOUT_MILLIS =
+            "intermediate_state_voip_normal_timeout_millis";
+
+    /**
+     * A prefix to use for {@link DeviceConfig} for the intermediate state timeout of
+     * VoIP emergency call, in millis.
+     */
+    private static final String INTERMEDIATE_STATE_VOIP_EMERGENCY_TIMEOUT_MILLIS =
+            "intermediate_state_voip_emergency_timeout_millis";
+
+    /**
+     * A prefix to use for {@link DeviceConfig} for the intermediate state timeout of
+     * non-VoIP call, in millis.
+     */
+    private static final String INTERMEDIATE_STATE_NON_VOIP_NORMAL_TIMEOUT_MILLIS =
+            "intermediate_state_non_voip_normal_timeout_millis";
+
+    /**
+     * A prefix to use for {@link DeviceConfig} for the intermediate state timeout of
+     * non-VoIP emergency call, in millis.
+     */
+    private static final String INTERMEDIATE_STATE_NON_VOIP_EMERGENCY_TIMEOUT_MILLIS =
+            "intermediate_state_non_voip_emergency_timeout_millis";
+
     private Timeouts() {
     }
 
@@ -237,6 +359,116 @@
         return get(contentResolver, "call_diagnostic_service_timeout", 2000L /* 2 sec */);
     }
 
+    /**
+     * Returns the duration of time a VoIP call can be in a transitory state before Telecom will
+     * try to clean up the call.
+     * @return the state timeout in millis.
+     */
+    public static long getVoipCallTransitoryStateTimeoutMillis() {
+        return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
+                TRANSITORY_STATE_VOIP_NORMAL_TIMEOUT_MILLIS, 5000L);
+    }
+
+
+    /**
+     * Returns the threshold used to filter out ecalls that the user may have dialed by mistake
+     * It is used only when the disconnect cause is LOCAL by EmergencyDiagnosticLogger
+     * @return the threshold in milliseconds
+     */
+    public static long getEmergencyCallTimeBeforeUserDisconnectThresholdMillis() {
+        return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
+                EMERGENCY_CALL_TIME_BEFORE_USER_DISCONNECT_THRESHOLD_MILLIS, 20000L);
+    }
+
+    /**
+     * Returns the threshold used to detect ecalls that transition to active but only for a very
+     * short duration. These short duration active calls can result in Diagnostic data collection.
+     * @return the threshold in milliseconds
+     */
+    public static long getEmergencyCallActiveTimeThresholdMillis() {
+        return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
+                EMERGENCY_CALL_ACTIVE_TIME_THRESHOLD_MILLIS, 15000L);
+    }
+
+    /**
+     * Time in Days that is used to filter out old dropbox entries for emergency call diagnostic
+     * data. Entries older than this are ignored
+     */
+    public static int getDaysBackToSearchEmergencyDiagnosticEntries() {
+        return DeviceConfig.getInt(DeviceConfig.NAMESPACE_TELEPHONY,
+                DAYS_BACK_TO_SEARCH_EMERGENCY_DROP_BOX_ENTRIES, 30);
+    }
+
+    /**
+     * Returns the duration of time an emergency VoIP call can be in a transitory state before
+     * Telecom will try to clean up the call.
+     * @return the state timeout in millis.
+     */
+    public static long getVoipEmergencyCallTransitoryStateTimeoutMillis() {
+        return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
+                TRANSITORY_STATE_VOIP_EMERGENCY_TIMEOUT_MILLIS, 5000L);
+    }
+
+    /**
+     * Returns the duration of time a non-VoIP call can be in a transitory state before Telecom
+     * will try to clean up the call.
+     * @return the state timeout in millis.
+     */
+    public static long getNonVoipCallTransitoryStateTimeoutMillis() {
+        return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
+                TRANSITORY_STATE_NON_VOIP_NORMAL_TIMEOUT_MILLIS, 10000L);
+    }
+
+    /**
+     * Returns the duration of time an emergency non-VoIp call can be in a transitory state before
+     * Telecom will try to clean up the call.
+     * @return the state timeout in millis.
+     */
+    public static long getNonVoipEmergencyCallTransitoryStateTimeoutMillis() {
+        return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
+                TRANSITORY_STATE_NON_VOIP_EMERGENCY_TIMEOUT_MILLIS, 10000L);
+    }
+
+    /**
+     * Returns the duration of time a VoIP call can be in an intermediate state before Telecom will
+     * try to clean up the call.
+     * @return the state timeout in millis.
+     */
+    public static long getVoipCallIntermediateStateTimeoutMillis() {
+        return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
+                INTERMEDIATE_STATE_VOIP_NORMAL_TIMEOUT_MILLIS, 60000L);
+    }
+
+    /**
+     * Returns the duration of time an emergency VoIP call can be in an intermediate state before
+     * Telecom will try to clean up the call.
+     * @return the state timeout in millis.
+     */
+    public static long getVoipEmergencyCallIntermediateStateTimeoutMillis() {
+        return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
+                INTERMEDIATE_STATE_VOIP_EMERGENCY_TIMEOUT_MILLIS, 60000L);
+    }
+
+    /**
+     * Returns the duration of time a non-VoIP call can be in an intermediate state before Telecom
+     * will try to clean up the call.
+     * @return the state timeout in millis.
+     */
+    public static long getNonVoipCallIntermediateStateTimeoutMillis() {
+        return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
+                INTERMEDIATE_STATE_NON_VOIP_NORMAL_TIMEOUT_MILLIS, 120000L);
+    }
+
+    /**
+     * Returns the duration of time an emergency non-VoIP call can be in an intermediate state
+     * before Telecom will try to clean up the call.
+     * @return the state timeout in millis.
+     */
+    public static long getNonVoipEmergencyCallIntermediateStateTimeoutMillis() {
+        return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
+                INTERMEDIATE_STATE_NON_VOIP_EMERGENCY_TIMEOUT_MILLIS, 60000L);
+    }
+
     public static long getCallStartAppOpDebounceIntervalMillis() {
         return DeviceConfig.getLong(DeviceConfig.NAMESPACE_PRIVACY, "app_op_debounce_time", 250L);
     }
diff --git a/src/com/android/server/telecom/TransactionalServiceRepository.java b/src/com/android/server/telecom/TransactionalServiceRepository.java
new file mode 100644
index 0000000..f84b934
--- /dev/null
+++ b/src/com/android/server/telecom/TransactionalServiceRepository.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 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.server.telecom;
+
+import android.telecom.PhoneAccountHandle;
+
+import com.android.internal.telecom.ICallEventCallback;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Tracks all TransactionalServiceWrappers that have an ongoing call. Removes wrappers that have no
+ * more calls.
+ */
+public class TransactionalServiceRepository {
+
+    private static final Map<PhoneAccountHandle, TransactionalServiceWrapper> lookupTable =
+            new HashMap<>();
+
+    public TransactionalServiceRepository() {
+    }
+
+    public TransactionalServiceWrapper addNewCallForTransactionalServiceWrapper
+            (PhoneAccountHandle phoneAccountHandle, ICallEventCallback callEventCallback,
+                    CallsManager callsManager, Call call) {
+
+        TransactionalServiceWrapper service = null;
+        if (!hasExistingServiceWrapper(phoneAccountHandle)) {
+            service = new TransactionalServiceWrapper(callEventCallback,
+                    callsManager, phoneAccountHandle, call, this);
+        } else {
+            service = getTransactionalServiceWrapper(phoneAccountHandle);
+            if (service == null) {
+                throw new IllegalStateException("service is null");
+            } else {
+                service.trackCall(call);
+            }
+        }
+
+        lookupTable.put(phoneAccountHandle, service);
+
+        return service;
+    }
+
+    public TransactionalServiceWrapper getTransactionalServiceWrapper(PhoneAccountHandle pah) {
+        return lookupTable.get(pah);
+    }
+
+    public boolean hasExistingServiceWrapper(PhoneAccountHandle pah) {
+        return lookupTable.containsKey(pah);
+    }
+
+    public boolean removeServiceWrapper(PhoneAccountHandle pah) {
+        if (!hasExistingServiceWrapper(pah)) {
+            return false;
+        }
+        lookupTable.remove(pah);
+        return true;
+    }
+
+}
diff --git a/src/com/android/server/telecom/TransactionalServiceWrapper.java b/src/com/android/server/telecom/TransactionalServiceWrapper.java
new file mode 100644
index 0000000..90acba8
--- /dev/null
+++ b/src/com/android/server/telecom/TransactionalServiceWrapper.java
@@ -0,0 +1,663 @@
+/*
+ * Copyright (C) 2022 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.server.telecom;
+
+import static android.telecom.CallException.CODE_CALL_IS_NOT_BEING_TRACKED;
+import static android.telecom.CallException.TRANSACTION_EXCEPTION_KEY;
+import static android.telecom.TelecomManager.TELECOM_TRANSACTION_SUCCESS;
+
+import android.content.ComponentName;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.OutcomeReceiver;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.telecom.CallEndpoint;
+import android.telecom.CallException;
+import android.telecom.CallStreamingService;
+import android.telecom.DisconnectCause;
+import android.telecom.Log;
+import android.telecom.PhoneAccountHandle;
+import android.text.TextUtils;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.internal.telecom.ICallControl;
+import com.android.internal.telecom.ICallEventCallback;
+import com.android.server.telecom.voip.AnswerCallTransaction;
+import com.android.server.telecom.voip.CallEventCallbackAckTransaction;
+import com.android.server.telecom.voip.EndpointChangeTransaction;
+import com.android.server.telecom.voip.HoldCallTransaction;
+import com.android.server.telecom.voip.EndCallTransaction;
+import com.android.server.telecom.voip.HoldActiveCallForNewCallTransaction;
+import com.android.server.telecom.voip.ParallelTransaction;
+import com.android.server.telecom.voip.RequestFocusTransaction;
+import com.android.server.telecom.voip.SerialTransaction;
+import com.android.server.telecom.voip.TransactionManager;
+import com.android.server.telecom.voip.VoipCallTransaction;
+import com.android.server.telecom.voip.VoipCallTransactionResult;
+
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Locale;
+import java.util.Set;
+
+/**
+ * Implements {@link android.telecom.CallEventCallback} and {@link android.telecom.CallControl}
+ * on a per-client basis which is tied to a {@link PhoneAccountHandle}
+ */
+public class TransactionalServiceWrapper implements
+        ConnectionServiceFocusManager.ConnectionServiceFocus, IBinder.DeathRecipient {
+    private static final String TAG = TransactionalServiceWrapper.class.getSimpleName();
+
+    // CallControl : Client (ex. voip app) --> Telecom
+    public static final String SET_ACTIVE = "SetActive";
+    public static final String SET_INACTIVE = "SetInactive";
+    public static final String ANSWER = "Answer";
+    public static final String DISCONNECT = "Disconnect";
+    public static final String START_STREAMING = "StartStreaming";
+
+    // CallEventCallback : Telecom --> Client (ex. voip app)
+    public static final String ON_SET_ACTIVE = "onSetActive";
+    public static final String ON_SET_INACTIVE = "onSetInactive";
+    public static final String ON_ANSWER = "onAnswer";
+    public static final String ON_DISCONNECT = "onDisconnect";
+    public static final String ON_STREAMING_STARTED = "onStreamingStarted";
+
+    private final CallsManager mCallsManager;
+    private final ICallEventCallback mICallEventCallback;
+    private final PhoneAccountHandle mPhoneAccountHandle;
+    private final TransactionalServiceRepository mRepository;
+    private ConnectionServiceFocusManager.ConnectionServiceFocusListener mConnSvrFocusListener;
+    // init when constructor is called
+    private final Hashtable<String, Call> mTrackedCalls = new Hashtable<>();
+    private final TelecomSystem.SyncRoot mLock;
+    private final String mPackageName;
+    // needs to be non-final for testing
+    private TransactionManager mTransactionManager;
+    private CallStreamingController mStreamingController;
+
+    public TransactionalServiceWrapper(ICallEventCallback callEventCallback,
+            CallsManager callsManager, PhoneAccountHandle phoneAccountHandle, Call call,
+            TransactionalServiceRepository repo) {
+        // passed args
+        mICallEventCallback = callEventCallback;
+        mCallsManager = callsManager;
+        mPhoneAccountHandle = phoneAccountHandle;
+        mTrackedCalls.put(call.getId(), call); // service is now tracking its first call
+        mRepository = repo;
+        // init instance vars
+        mPackageName = phoneAccountHandle.getComponentName().getPackageName();
+        mTransactionManager = TransactionManager.getInstance();
+        mStreamingController = mCallsManager.getCallStreamingController();
+        mLock = mCallsManager.getLock();
+    }
+
+    @VisibleForTesting
+    public void setTransactionManager(TransactionManager transactionManager) {
+        mTransactionManager = transactionManager;
+    }
+
+    public TransactionManager getTransactionManager() {
+        return mTransactionManager;
+    }
+
+    public PhoneAccountHandle getPhoneAccountHandle() {
+        return mPhoneAccountHandle;
+    }
+
+    public void trackCall(Call call) {
+        synchronized (mLock) {
+            if (call != null) {
+                mTrackedCalls.put(call.getId(), call);
+            }
+        }
+    }
+
+    public Call getCallById(String callId) {
+        synchronized (mLock) {
+            return mTrackedCalls.get(callId);
+        }
+    }
+
+    @VisibleForTesting
+    public boolean untrackCall(Call call) {
+        Call removedCall = null;
+        synchronized (mLock) {
+            if (call != null) {
+                removedCall = mTrackedCalls.remove(call.getId());
+                if (mTrackedCalls.size() == 0) {
+                    mRepository.removeServiceWrapper(mPhoneAccountHandle);
+                }
+            }
+        }
+        Log.i(TAG, "removedCall call=" + removedCall);
+        return removedCall != null;
+    }
+
+    @VisibleForTesting
+    public int getNumberOfTrackedCalls() {
+        int callCount = 0;
+        synchronized (mLock) {
+            callCount = mTrackedCalls.size();
+        }
+        return callCount;
+    }
+
+    @Override
+    public void binderDied() {
+        // remove all tacked calls from CallsManager && frameworks side
+        for (String id : mTrackedCalls.keySet()) {
+            Call call = mTrackedCalls.get(id);
+            mCallsManager.markCallAsDisconnected(call, new DisconnectCause(DisconnectCause.ERROR));
+            mCallsManager.removeCall(call);
+            // remove calls from Frameworks side
+            if (mICallEventCallback != null) {
+                try {
+                    mICallEventCallback.removeCallFromTransactionalServiceWrapper(call.getId());
+                } catch (RemoteException e) {
+                    // pass
+                }
+            }
+        }
+        mTrackedCalls.clear();
+    }
+
+    /***
+     *********************************************************************************************
+     **                        ICallControl: Client --> Server                                **
+     **********************************************************************************************
+     */
+    public final ICallControl mICallControl = new ICallControl.Stub() {
+        @Override
+        public void setActive(String callId, android.os.ResultReceiver callback)
+                throws RemoteException {
+            try {
+                Log.startSession("TSW.sA");
+                createTransactions(callId, callback, SET_ACTIVE);
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
+        public void answer(int videoState, String callId, android.os.ResultReceiver callback)
+                throws RemoteException {
+            try {
+                Log.startSession("TSW.a");
+                createTransactions(callId, callback, ANSWER, videoState);
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
+        public void setInactive(String callId, android.os.ResultReceiver callback)
+                throws RemoteException {
+            try {
+                Log.startSession("TSW.sI");
+                createTransactions(callId, callback, SET_INACTIVE);
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
+        public void disconnect(String callId, DisconnectCause disconnectCause,
+                android.os.ResultReceiver callback)
+                throws RemoteException {
+            try {
+                Log.startSession("TSW.d");
+                createTransactions(callId, callback, DISCONNECT, disconnectCause);
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
+        public void startCallStreaming(String callId, android.os.ResultReceiver callback)
+                throws RemoteException {
+            try {
+                Log.startSession("TSW.sCS");
+                createTransactions(callId, callback, START_STREAMING);
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        private void createTransactions(String callId, ResultReceiver callback, String action,
+                Object... objects) {
+            Log.d(TAG, "createTransactions: callId=" + callId);
+            Call call = mTrackedCalls.get(callId);
+            if (call != null) {
+                switch (action) {
+                    case SET_ACTIVE:
+                        addTransactionsToManager(createSetActiveTransactions(call), callback);
+                        break;
+                    case ANSWER:
+                        addTransactionsToManager(createSetAnswerTransactions(call,
+                                (int) objects[0]), callback);
+                        break;
+                    case DISCONNECT:
+                        addTransactionsToManager(new EndCallTransaction(mCallsManager,
+                                (DisconnectCause) objects[0], call), callback);
+                        break;
+                    case SET_INACTIVE:
+                        addTransactionsToManager(
+                                new HoldCallTransaction(mCallsManager, call), callback);
+                        break;
+                    case START_STREAMING:
+                        addTransactionsToManager(createStartStreamingTransaction(call), callback);
+                        break;
+                }
+            } else {
+                Bundle exceptionBundle = new Bundle();
+                exceptionBundle.putParcelable(TRANSACTION_EXCEPTION_KEY,
+                        new CallException(TextUtils.formatSimple(
+                        "Telecom cannot process [%s] because the call with id=[%s] is no longer "
+                                + "being tracked. This is most likely a result of the call "
+                                + "already being disconnected and removed. Try re-adding the call"
+                                + " via TelecomManager#addCall", action, callId),
+                                CODE_CALL_IS_NOT_BEING_TRACKED));
+                callback.send(CODE_CALL_IS_NOT_BEING_TRACKED, exceptionBundle);
+            }
+        }
+
+        @Override
+        public void requestCallEndpointChange(CallEndpoint endpoint, ResultReceiver callback) {
+            try {
+                Log.startSession("TSW.rCEC");
+                addTransactionsToManager(new EndpointChangeTransaction(endpoint, mCallsManager),
+                        callback);
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        /**
+         * Application would like to inform InCallServices of an event
+         */
+        @Override
+        public void sendEvent(String callId, String event, Bundle extras) {
+            try {
+                Log.startSession("TSW.sE");
+                Call call = mTrackedCalls.get(callId);
+                if (call != null) {
+                    call.onConnectionEvent(event, extras);
+                }
+                else{
+                    Log.i(TAG,
+                            "sendEvent: was called but there is no call with id=[%s] cannot be "
+                                    + "found. Most likely the call has been disconnected");
+                }
+            }
+            finally {
+                Log.endSession();
+            }
+        }
+    };
+
+    private void addTransactionsToManager(VoipCallTransaction transaction,
+            ResultReceiver callback) {
+        Log.d(TAG, "addTransactionsToManager");
+
+        mTransactionManager.addTransaction(transaction, new OutcomeReceiver<>() {
+            @Override
+            public void onResult(VoipCallTransactionResult result) {
+                Log.d(TAG, "addTransactionsToManager: onResult:");
+                callback.send(TELECOM_TRANSACTION_SUCCESS, new Bundle());
+            }
+
+            @Override
+            public void onError(CallException exception) {
+                Log.d(TAG, "addTransactionsToManager: onError");
+                Bundle extras = new Bundle();
+                extras.putParcelable(TRANSACTION_EXCEPTION_KEY, exception);
+                callback.send(exception == null ? CallException.CODE_ERROR_UNKNOWN :
+                        exception.getCode(), extras);
+            }
+        });
+    }
+
+    public ICallControl getICallControl() {
+        return mICallControl;
+    }
+
+    /***
+     *********************************************************************************************
+     **                    ICallEventCallback: Server --> Client                                **
+     **********************************************************************************************
+     */
+
+    public void onSetActive(Call call) {
+        try {
+            Log.startSession("TSW.oSA");
+            Log.d(TAG, String.format(Locale.US, "onSetActive: callId=[%s]", call.getId()));
+            handleNewActiveCallCallbacks(call, ON_SET_ACTIVE, 0);
+        } finally {
+            Log.endSession();
+        }
+    }
+
+    public void onAnswer(Call call, int videoState) {
+        try {
+            Log.startSession("TSW.oA");
+            Log.d(TAG, String.format(Locale.US, "onAnswer: callId=[%s]", call.getId()));
+            handleNewActiveCallCallbacks(call, ON_ANSWER, videoState);
+        } finally {
+            Log.endSession();
+        }
+    }
+
+    // need to create multiple transactions for onSetActive and onAnswer which both seek to set
+    // the call to active
+    private void handleNewActiveCallCallbacks(Call call, String action, int videoState) {
+        // save CallsManager state before sending client state changes
+        Call foregroundCallBeforeSwap = mCallsManager.getForegroundCall();
+        boolean wasActive = foregroundCallBeforeSwap != null && foregroundCallBeforeSwap.isActive();
+
+        // create 3 serial transactions:
+        // -- hold active
+        // -- set newCall as active
+        // -- ack from client
+        SerialTransaction serialTransactions = createSetActiveTransactions(call);
+        serialTransactions.appendTransaction(
+                new CallEventCallbackAckTransaction(mICallEventCallback,
+                        action, call.getId(), videoState, mLock));
+
+        // do CallsManager workload before asking client and
+        //   reset CallsManager state if client does NOT ack
+        mTransactionManager.addTransaction(serialTransactions, new OutcomeReceiver<>() {
+            @Override
+            public void onResult(VoipCallTransactionResult result) {
+                Log.i(TAG, String.format(Locale.US,
+                        "%s: onResult: callId=[%s]", action, call.getId()));
+            }
+
+            @Override
+            public void onError(CallException exception) {
+                maybeResetForegroundCall(foregroundCallBeforeSwap, wasActive);
+            }
+        });
+    }
+
+
+    public void onSetInactive(Call call) {
+        try {
+            Log.startSession("TSW.oSI");
+            Log.i(TAG, String.format(Locale.US, "onSetInactive: callId=[%s]", call.getId()));
+            mTransactionManager.addTransaction(
+                    new CallEventCallbackAckTransaction(mICallEventCallback,
+                            ON_SET_INACTIVE, call.getId(), mLock), new OutcomeReceiver<>() {
+                        @Override
+                        public void onResult(VoipCallTransactionResult result) {
+                            mCallsManager.markCallAsOnHold(call);
+                        }
+
+                        @Override
+                        public void onError(CallException exception) {
+                            Log.i(TAG, "onSetInactive: onError: with e=[%e]", exception);
+                        }
+                    });
+        } finally {
+            Log.endSession();
+        }
+    }
+
+    public void onDisconnect(Call call, DisconnectCause cause) {
+        try {
+            Log.startSession("TSW.oD");
+            Log.d(TAG, String.format(Locale.US, "onDisconnect: callId=[%s]", call.getId()));
+
+            mTransactionManager.addTransaction(
+                    new CallEventCallbackAckTransaction(mICallEventCallback, ON_DISCONNECT,
+                            call.getId(), cause, mLock), new OutcomeReceiver<>() {
+                        @Override
+                        public void onResult(VoipCallTransactionResult result) {
+                            removeCallFromCallsManager(call, cause);
+                        }
+
+                        @Override
+                        public void onError(CallException exception) {
+                            removeCallFromCallsManager(call, cause);
+                        }
+                    }
+            );
+        } finally {
+            Log.endSession();
+        }
+    }
+
+    public void onCallStreamingStarted(Call call) {
+        try {
+            Log.startSession("TSW.oCSS");
+            Log.d(TAG, String.format(Locale.US, "onCallStreamingStarted: callId=[%s]",
+                    call.getId()));
+
+            mTransactionManager.addTransaction(
+                    new CallEventCallbackAckTransaction(mICallEventCallback, ON_STREAMING_STARTED,
+                            call.getId(), mLock), new OutcomeReceiver<>() {
+                        @Override
+                        public void onResult(VoipCallTransactionResult result) {
+                        }
+
+                        @Override
+                        public void onError(CallException exception) {
+                            Log.i(TAG, "onCallStreamingStarted: onError: with e=[%e]",
+                                    exception);
+                            stopCallStreaming(call);
+                        }
+                    }
+            );
+        } finally {
+            Log.endSession();
+        }
+    }
+
+    public void onCallStreamingFailed(Call call,
+            @CallStreamingService.StreamingFailedReason int streamingFailedReason) {
+        if (call != null) {
+            try {
+                mICallEventCallback.onCallStreamingFailed(call.getId(), streamingFailedReason);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    public void onCallEndpointChanged(Call call, CallEndpoint endpoint) {
+        if (call != null) {
+            try {
+                mICallEventCallback.onCallEndpointChanged(call.getId(), endpoint);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    public void onAvailableCallEndpointsChanged(Call call, Set<CallEndpoint> endpoints) {
+        if (call != null) {
+            try {
+                mICallEventCallback.onAvailableCallEndpointsChanged(call.getId(),
+                        endpoints.stream().toList());
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    public void onMuteStateChanged(Call call, boolean isMuted) {
+        if (call != null) {
+            try {
+                mICallEventCallback.onMuteStateChanged(call.getId(), isMuted);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    public void removeCallFromWrappers(Call call) {
+        if (call != null) {
+            try {
+                // remove the call from frameworks wrapper (client side)
+                mICallEventCallback.removeCallFromTransactionalServiceWrapper(call.getId());
+                // remove the call from this class/wrapper (server side)
+                untrackCall(call);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    public void onEvent(Call call, String event, Bundle extras){
+        if (call != null) {
+            try {
+                mICallEventCallback.onEvent(call.getId(), event, extras);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    /***
+     *********************************************************************************************
+     **                                Helpers                                                  **
+     **********************************************************************************************
+     */
+    private void maybeResetForegroundCall(Call foregroundCallBeforeSwap, boolean wasActive) {
+        if (foregroundCallBeforeSwap == null) {
+            return;
+        }
+        if (wasActive && !foregroundCallBeforeSwap.isActive()) {
+            mCallsManager.markCallAsActive(foregroundCallBeforeSwap);
+        }
+    }
+
+    private void removeCallFromCallsManager(Call call, DisconnectCause cause) {
+        if (cause.getCode() != DisconnectCause.REJECTED) {
+            mCallsManager.markCallAsDisconnected(call, cause);
+        }
+        mCallsManager.removeCall(call);
+    }
+
+    private SerialTransaction createSetActiveTransactions(Call call) {
+        // create list for multiple transactions
+        List<VoipCallTransaction> transactions = new ArrayList<>();
+
+        // add t1. hold potential active call
+        transactions.add(new HoldActiveCallForNewCallTransaction(mCallsManager, call));
+
+        // add t2. send request to set the current call active
+        transactions.add(new RequestFocusTransaction(mCallsManager, call));
+
+        // send off to Transaction Manager to process
+        return new SerialTransaction(transactions, mLock);
+    }
+
+    private SerialTransaction createSetAnswerTransactions(Call call, int videoState) {
+        // create list for multiple transactions
+        List<VoipCallTransaction> transactions = new ArrayList<>();
+
+        // add t1. hold potential active call
+        transactions.add(new HoldActiveCallForNewCallTransaction(mCallsManager, call));
+
+        // add t2. answer current call
+        transactions.add(new AnswerCallTransaction(mCallsManager, call, videoState));
+
+        // send off to Transaction Manager to process
+        return new SerialTransaction(transactions, mLock);
+    }
+
+    /***
+     *********************************************************************************************
+     **                    FocusManager                                                       **
+     **********************************************************************************************
+     */
+
+    @Override
+    public void connectionServiceFocusLost() {
+        if (mConnSvrFocusListener != null) {
+            mConnSvrFocusListener.onConnectionServiceReleased(this);
+        }
+        Log.i(TAG, String.format(Locale.US, "connectionServiceFocusLost for package=[%s]",
+                mPackageName));
+    }
+
+    @Override
+    public void connectionServiceFocusGained() {
+        Log.i(TAG, String.format(Locale.US, "connectionServiceFocusGained for package=[%s]",
+                mPackageName));
+    }
+
+    @Override
+    public void setConnectionServiceFocusListener(
+            ConnectionServiceFocusManager.ConnectionServiceFocusListener listener) {
+        mConnSvrFocusListener = listener;
+    }
+
+    @Override
+    public ComponentName getComponentName() {
+        return mPhoneAccountHandle.getComponentName();
+    }
+
+    /***
+     *********************************************************************************************
+     **                    CallStreaming                                                        **
+     *********************************************************************************************
+     */
+
+    private SerialTransaction createStartStreamingTransaction(Call call) {
+        // start streaming transaction flow:
+        //     make sure there's no ongoing streaming call --> bind to EXO
+        //                                                 `-> change audio mode
+        // create list for multiple transactions
+        List<VoipCallTransaction> transactions = new ArrayList<>();
+
+        // add t1. make sure no ongoing streaming call
+        transactions.add(new CallStreamingController.QueryCallStreamingTransaction(mCallsManager));
+
+        // create list for parallel transactions
+        List<VoipCallTransaction> subTransactions = new ArrayList<>();
+        // add t2.1 bind to call streaming service
+        subTransactions.add(mStreamingController.getCallStreamingServiceTransaction(
+                mCallsManager.getContext(), this, call));
+        // add t2.2 audio route operations
+        subTransactions.add(new CallStreamingController.AudioInterceptionTransaction(call,
+                true, mLock));
+
+        // add t2
+        transactions.add(new ParallelTransaction(subTransactions, mLock));
+        // send off to Transaction Manager to process
+        return new SerialTransaction(transactions, mLock);
+    }
+
+    private VoipCallTransaction createStopStreamingTransaction(Call call) {
+        // TODO: implement this
+        // Stop streaming transaction flow:
+        List<VoipCallTransaction> transactions = new ArrayList<>();
+
+        // 1. unbind to call streaming service
+        transactions.add(mStreamingController.getUnbindStreamingServiceTransaction());
+        // 2. audio route operations
+        transactions.add(new CallStreamingController.AudioInterceptionTransaction(call,
+                false, mLock));
+        return new ParallelTransaction(transactions, mLock);
+    }
+
+
+    public void stopCallStreaming(Call call) {
+        if (call != null && call.isStreaming()) {
+            VoipCallTransaction stopStreamingTransaction = createStopStreamingTransaction(call);
+            addTransactionsToManager(stopStreamingTransaction, new ResultReceiver(null));
+        }
+    }
+}
diff --git a/src/com/android/server/telecom/callfiltering/BlockedNumbersAdapter.java b/src/com/android/server/telecom/callfiltering/BlockedNumbersAdapter.java
new file mode 100644
index 0000000..b8658d8
--- /dev/null
+++ b/src/com/android/server/telecom/callfiltering/BlockedNumbersAdapter.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 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.server.telecom.callfiltering;
+
+import android.content.Context;
+
+/**
+ * Adapter interface that wraps methods from
+ * {@link android.provider.BlockedNumberContract.SystemContract} and
+ * {@link com.android.server.telecom.settings.BlockedNumbersUtil} to make things testable.
+ */
+public interface BlockedNumbersAdapter {
+    boolean shouldShowEmergencyCallNotification (Context context);
+    void updateEmergencyCallNotification(Context context, boolean showNotification);
+}
diff --git a/src/com/android/server/telecom/callfiltering/CallFilteringResult.java b/src/com/android/server/telecom/callfiltering/CallFilteringResult.java
index 84ce4d4..931d5bb 100644
--- a/src/com/android/server/telecom/callfiltering/CallFilteringResult.java
+++ b/src/com/android/server/telecom/callfiltering/CallFilteringResult.java
@@ -29,6 +29,7 @@
         private boolean mShouldReject;
         private boolean mShouldAddToCallLog;
         private boolean mShouldShowNotification;
+        private boolean mDndSuppressed = false;
         private boolean mShouldSilence = false;
         private boolean mShouldScreenViaAudio = false;
         private boolean mContactExists = false;
@@ -58,6 +59,11 @@
             return this;
         }
 
+        public Builder setDndSuppressed(boolean shouldPerformCheck) {
+            mDndSuppressed = shouldPerformCheck;
+            return this;
+        }
+
         public Builder setShouldSilence(boolean shouldSilence) {
             mShouldSilence = shouldSilence;
             return this;
@@ -101,6 +107,7 @@
                     .setShouldReject(result.shouldReject)
                     .setShouldAddToCallLog(result.shouldAddToCallLog)
                     .setShouldShowNotification(result.shouldShowNotification)
+                    .setDndSuppressed(result.shouldSuppressCallDueToDndStatus)
                     .setShouldSilence(result.shouldSilence)
                     .setCallBlockReason(result.mCallBlockReason)
                     .setShouldScreenViaAudio(result.shouldScreenViaAudio)
@@ -113,8 +120,9 @@
 
         public CallFilteringResult build() {
             return new CallFilteringResult(mShouldAllowCall, mShouldReject, mShouldSilence,
-                    mShouldAddToCallLog, mShouldShowNotification, mCallBlockReason,
-                    mCallScreeningAppName, mCallScreeningComponentName, mCallScreeningResponse,
+                    mShouldAddToCallLog, mShouldShowNotification,
+                    mDndSuppressed, mCallBlockReason, mCallScreeningAppName,
+                    mCallScreeningComponentName, mCallScreeningResponse,
                     mIsResponseFromSystemDialer, mShouldScreenViaAudio, mContactExists);
         }
     }
@@ -125,6 +133,7 @@
     public boolean shouldAddToCallLog;
     public boolean shouldScreenViaAudio = false;
     public boolean shouldShowNotification;
+    public boolean shouldSuppressCallDueToDndStatus = false;
     public int mCallBlockReason;
     public CharSequence mCallScreeningAppName;
     public String mCallScreeningComponentName;
@@ -133,8 +142,9 @@
     public boolean contactExists;
 
     private CallFilteringResult(boolean shouldAllowCall, boolean shouldReject, boolean
-            shouldSilence, boolean shouldAddToCallLog, boolean shouldShowNotification, int
-            callBlockReason, CharSequence callScreeningAppName, String callScreeningComponentName,
+            shouldSilence, boolean shouldAddToCallLog, boolean shouldShowNotification, boolean
+            shouldSuppress, int callBlockReason, CharSequence callScreeningAppName,
+            String callScreeningComponentName,
             CallScreeningService.ParcelableCallResponse callScreeningResponse,
             boolean isResponseFromSystemDialer,
             boolean shouldScreenViaAudio, boolean contactExists) {
@@ -143,6 +153,7 @@
         this.shouldSilence = shouldSilence;
         this.shouldAddToCallLog = shouldAddToCallLog;
         this.shouldShowNotification = shouldShowNotification;
+        this.shouldSuppressCallDueToDndStatus = shouldSuppress;
         this.shouldScreenViaAudio = shouldScreenViaAudio;
         this.mCallBlockReason = callBlockReason;
         this.mCallScreeningAppName = callScreeningAppName;
@@ -202,6 +213,8 @@
                 .setShouldAddToCallLog(shouldAddToCallLog && other.shouldAddToCallLog)
                 .setShouldShowNotification(shouldShowNotification && other.shouldShowNotification)
                 .setShouldScreenViaAudio(shouldScreenViaAudio || other.shouldScreenViaAudio)
+                .setDndSuppressed(shouldSuppressCallDueToDndStatus
+                        || other.shouldSuppressCallDueToDndStatus)
                 .setContactExists(contactExists || other.contactExists);
         combineScreeningResponses(b, this, other);
         return b.build();
@@ -228,6 +241,8 @@
                 .setShouldSilence(shouldSilence || other.shouldSilence)
                 .setShouldAddToCallLog(shouldAddToCallLog && other.shouldAddToCallLog)
                 .setShouldShowNotification(shouldShowNotification && other.shouldShowNotification)
+                .setDndSuppressed(shouldSuppressCallDueToDndStatus
+                        || other.shouldSuppressCallDueToDndStatus)
                 .setShouldScreenViaAudio(shouldScreenViaAudio || other.shouldScreenViaAudio)
                 .setCallBlockReason(callBlockReason)
                 .setCallScreeningAppName(callScreeningAppName)
@@ -272,6 +287,7 @@
         if (shouldSilence != that.shouldSilence) return false;
         if (shouldAddToCallLog != that.shouldAddToCallLog) return false;
         if (shouldShowNotification != that.shouldShowNotification) return false;
+        if (shouldSuppressCallDueToDndStatus != that.shouldSuppressCallDueToDndStatus) return false;
         if (mCallBlockReason != that.mCallBlockReason) return false;
         if (contactExists != that.contactExists) return false;
 
@@ -318,6 +334,10 @@
             sb.append(", notified");
         }
 
+        if (shouldSuppressCallDueToDndStatus) {
+            sb.append(", DND suppressed");
+        }
+
         if (contactExists) {
             sb.append(", contact exists");
         }
diff --git a/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
index 5f4c40b..f542fa2 100644
--- a/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
+++ b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
@@ -24,6 +24,7 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.UserHandle;
 import android.provider.CallLog;
 import android.telecom.CallScreeningService;
 import android.telecom.Log;
@@ -318,7 +319,7 @@
         CallScreeningServiceConnection connection = new CallScreeningServiceConnection(
                 resultFuture);
         if (!CallScreeningServiceHelper.bindCallScreeningService(mContext,
-                mCallsManager.getCurrentUserHandle(), mPackageName, connection)) {
+                mCall.getUserHandleFromTargetPhoneAccount(), mPackageName, connection)) {
             Log.i(this, "Call screening service binding failed.");
             resultFuture.complete(mPriorStageResult);
         } else {
diff --git a/src/com/android/server/telecom/callfiltering/DndCallFilter.java b/src/com/android/server/telecom/callfiltering/DndCallFilter.java
new file mode 100644
index 0000000..f6ed646
--- /dev/null
+++ b/src/com/android/server/telecom/callfiltering/DndCallFilter.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2022 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.server.telecom.callfiltering;
+
+import android.telecom.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.LogUtils;
+import com.android.server.telecom.Ringer;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+
+/**
+ * DndCallFilter is a incoming call filter that adds the
+ * {@link android.telecom.Call.EXTRA_IS_SUPPRESSED_BY_DO_NOT_DISTURB } early in the call processing.
+ * Adding {@link android.telecom.Call.EXTRA_IS_SUPPRESSED_BY_DO_NOT_DISTURB } before the Call object
+ * is passed to all InCallServices is crucial for InCallServices that may disrupt the user and
+ * potentially bypass the current Do Not Disturb settings.
+ */
+public class DndCallFilter extends CallFilter {
+
+    private final Call mCall;
+    private final Ringer mRinger;
+
+    public DndCallFilter(Call call, Ringer ringer) {
+        mCall = call;
+        mRinger = ringer;
+    }
+
+    @VisibleForTesting
+    @Override
+    public CompletionStage<CallFilteringResult> startFilterLookup(CallFilteringResult result) {
+        CompletableFuture<CallFilteringResult> resultFuture = new CompletableFuture<>();
+
+        // start timer for query to NotificationManager
+        Log.addEvent(mCall, LogUtils.Events.DND_PRE_CHECK_INITIATED);
+
+        // query NotificationManager to determine if the call should ring or be suppressed
+        boolean shouldSuppress = !mRinger.shouldRingForContact(mCall);
+
+        // end timer
+        Log.addEvent(mCall, LogUtils.Events.DND_PRE_CHECK_COMPLETED, shouldSuppress);
+
+        // complete the resultFuture object
+        resultFuture.complete(new CallFilteringResult.Builder()
+                .setShouldAllowCall(true)
+                .setShouldAddToCallLog(true)
+                .setShouldShowNotification(true)
+                .setDndSuppressed(shouldSuppress)
+                .build());
+
+        return resultFuture;
+    }
+
+}
diff --git a/src/com/android/server/telecom/callfiltering/IncomingCallFilterGraph.java b/src/com/android/server/telecom/callfiltering/IncomingCallFilterGraph.java
index 9fa864e..d79e80e 100644
--- a/src/com/android/server/telecom/callfiltering/IncomingCallFilterGraph.java
+++ b/src/com/android/server/telecom/callfiltering/IncomingCallFilterGraph.java
@@ -41,6 +41,7 @@
                     .setShouldReject(false)
                     .setShouldAddToCallLog(true)
                     .setShouldShowNotification(true)
+                    .setDndSuppressed(false)
                     .build();
 
     private final CallFilterResultCallback mListener;
@@ -143,6 +144,7 @@
                 .setShouldSilence(false)
                 .setShouldAddToCallLog(true)
                 .setShouldShowNotification(true)
+                .setDndSuppressed(false)
                 .build();
         for (CallFilter dependencyFilter : filter.getDependencies()) {
             result = result.combine(dependencyFilter.getResult());
diff --git a/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java b/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java
index 7469650..963e923 100644
--- a/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java
+++ b/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java
@@ -74,7 +74,7 @@
             mServiceType = serviceType;
         }
 
-        private void process() {
+        private void process(UserHandle userHandleForCallRedirection) {
             Intent intent = new Intent(CallRedirectionService.SERVICE_INTERFACE)
                     .setComponent(mComponentName);
             ServiceConnection connection = new CallRedirectionServiceConnection();
@@ -83,7 +83,7 @@
                     connection,
                     Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
                     | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS,
-                    UserHandle.CURRENT)) {
+                    userHandleForCallRedirection)) {
                 Log.d(this, "bindService, found " + mServiceType + " call redirection service,"
                         + " waiting for it to connect");
                 mConnection = connection;
@@ -340,7 +340,8 @@
                                 mPhoneAccountHandle, mRedirectionGatewayInfo, mSpeakerphoneOn,
                                 mVideoState, mShouldCancelCall, mUiAction);
                     } else {
-                        performCarrierCallRedirection();
+                        // Use the current user for carrier call redirection
+                        performCarrierCallRedirection(UserHandle.CURRENT);
                     }
                 } else if (mIsCarrierRedirectionPending) {
                     Log.addEvent(mCall, LogUtils.Events.REDIRECTION_COMPLETED_CARRIER);
@@ -356,39 +357,41 @@
     /**
      * The entry to perform call redirection of the call from (@link CallsManager)
      */
-    public void performCallRedirection() {
+    public void performCallRedirection(UserHandle userHandleForCallRedirection) {
         // If the Gateway Info is set with intent, only request with carrier call redirection.
         if (mRedirectionGatewayInfo != null) {
-            performCarrierCallRedirection();
+            // Use the current user for carrier call redirection
+            performCarrierCallRedirection(UserHandle.CURRENT);
         } else {
-            performUserDefinedCallRedirection();
+            performUserDefinedCallRedirection(userHandleForCallRedirection);
         }
     }
 
-    private void performUserDefinedCallRedirection() {
+    private void performUserDefinedCallRedirection(UserHandle userHandleForCallRedirection) {
         Log.d(this, "performUserDefinedCallRedirection");
         ComponentName componentName =
-                mCallRedirectionProcessorHelper.getUserDefinedCallRedirectionService();
+                mCallRedirectionProcessorHelper.
+                        getUserDefinedCallRedirectionService(userHandleForCallRedirection);
         if (componentName != null) {
             mAttempt = new CallRedirectionAttempt(componentName, SERVICE_TYPE_USER_DEFINED);
-            mAttempt.process();
+            mAttempt.process(userHandleForCallRedirection);
             mIsUserDefinedRedirectionPending = true;
             processTimeoutForCallRedirection(SERVICE_TYPE_USER_DEFINED);
         } else {
             Log.i(this, "There are no user-defined call redirection services installed on this"
                     + " device.");
-            performCarrierCallRedirection();
+            performCarrierCallRedirection(UserHandle.CURRENT);
         }
     }
 
-    private void performCarrierCallRedirection() {
+    private void performCarrierCallRedirection(UserHandle userHandleForCallRedirection) {
         Log.d(this, "performCarrierCallRedirection");
         ComponentName componentName =
                 mCallRedirectionProcessorHelper.getCarrierCallRedirectionService(
                         mPhoneAccountHandle);
         if (componentName != null) {
             mAttempt = new CallRedirectionAttempt(componentName, SERVICE_TYPE_CARRIER);
-            mAttempt.process();
+            mAttempt.process(userHandleForCallRedirection);
             mIsCarrierRedirectionPending = true;
             processTimeoutForCallRedirection(SERVICE_TYPE_CARRIER);
         } else {
@@ -429,18 +432,22 @@
     }
 
     /**
-     * Checks if Telecom can make call redirection with any available call redirection service.
+     * Checks if Telecom can make call redirection with any available call redirection service
+     * as the specified user.
      *
      * @return {@code true} if it can; {@code false} otherwise.
      */
-    public boolean canMakeCallRedirectionWithService() {
-        boolean canMakeCallRedirectionWithService =
-                mCallRedirectionProcessorHelper.getUserDefinedCallRedirectionService() != null
+    public boolean canMakeCallRedirectionWithServiceAsUser(
+            UserHandle userHandleForCallRedirection) {
+        boolean canMakeCallRedirectionWithServiceAsUser =
+                mCallRedirectionProcessorHelper
+                        .getUserDefinedCallRedirectionService(userHandleForCallRedirection) != null
                         || mCallRedirectionProcessorHelper.getCarrierCallRedirectionService(
                                 mPhoneAccountHandle) != null;
-        Log.i(this, "Can make call redirection with any available service: "
-                + canMakeCallRedirectionWithService);
-        return canMakeCallRedirectionWithService;
+        Log.i(this, "Can make call redirection with any "
+                + "available service as user (" + userHandleForCallRedirection
+                + ") : " + canMakeCallRedirectionWithServiceAsUser);
+        return canMakeCallRedirectionWithServiceAsUser;
     }
 
     /**
diff --git a/src/com/android/server/telecom/callredirection/CallRedirectionProcessorHelper.java b/src/com/android/server/telecom/callredirection/CallRedirectionProcessorHelper.java
index 9771d65..7a2d415 100644
--- a/src/com/android/server/telecom/callredirection/CallRedirectionProcessorHelper.java
+++ b/src/com/android/server/telecom/callredirection/CallRedirectionProcessorHelper.java
@@ -23,6 +23,7 @@
 import android.content.pm.ResolveInfo;
 import android.net.Uri;
 import android.os.PersistableBundle;
+import android.os.UserHandle;
 import android.telecom.CallRedirectionService;
 import android.telecom.GatewayInfo;
 import android.telecom.Log;
@@ -53,8 +54,9 @@
     }
 
     @VisibleForTesting
-    public ComponentName getUserDefinedCallRedirectionService() {
-        String packageName = mCallsManager.getRoleManagerAdapter().getDefaultCallRedirectionApp();
+    public ComponentName getUserDefinedCallRedirectionService(UserHandle userHandle) {
+        String packageName = mCallsManager.getRoleManagerAdapter().getDefaultCallRedirectionApp(
+                userHandle);
         if (TextUtils.isEmpty(packageName)) {
             Log.i(this, "PackageName is empty. Not performing user-defined call redirection.");
             return null;
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index 9ad0da4..ef85fc7 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -26,9 +26,11 @@
 import android.os.PowerManager;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.provider.BlockedNumberContract;
 import android.telecom.Log;
 
 import android.telecom.CallerInfoAsyncQuery;
+import android.view.accessibility.AccessibilityManager;
 
 import com.android.internal.telecom.IInternalServiceRetriever;
 import com.android.internal.telecom.ITelecomLoader;
@@ -53,14 +55,19 @@
 import com.android.server.telecom.ProximitySensorManagerFactory;
 import com.android.server.telecom.InCallWakeLockController;
 import com.android.server.telecom.ProximitySensorManager;
+import com.android.server.telecom.Ringer;
 import com.android.server.telecom.RoleManagerAdapterImpl;
 import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.TelecomWakeLock;
 import com.android.server.telecom.Timeouts;
+import com.android.server.telecom.callfiltering.BlockedNumbersAdapter;
+import com.android.server.telecom.settings.BlockedNumbersUtil;
 import com.android.server.telecom.ui.IncomingCallNotifier;
 import com.android.server.telecom.ui.MissedCallNotifierImpl;
 import com.android.server.telecom.ui.NotificationChannelManager;
 
+import java.util.concurrent.Executors;
+
 /**
  * Implementation of the ITelecom interface.
  */
@@ -154,7 +161,7 @@
                             new InCallWakeLockControllerFactory() {
                                 @Override
                                 public InCallWakeLockController create(Context context,
-                                                                       CallsManager callsManager) {
+                                        CallsManager callsManager) {
                                     return new InCallWakeLockController(
                                             new TelecomWakeLock(context,
                                                     PowerManager.FULL_WAKE_LOCK,
@@ -191,7 +198,38 @@
                             new RoleManagerAdapterImpl(context,
                                     (RoleManager) context.getSystemService(Context.ROLE_SERVICE)),
                             new ContactsAsyncHelper.Factory(),
-                            internalServiceRetriever.getDeviceIdleController()));
+                            internalServiceRetriever.getDeviceIdleController(),
+                            new Ringer.AccessibilityManagerAdapter() {
+                                @Override
+                                public boolean startFlashNotificationSequence(
+                                        @androidx.annotation.NonNull Context context, int reason) {
+                                    return context.getSystemService(AccessibilityManager.class)
+                                            .startFlashNotificationSequence(context, reason);
+                                }
+
+                                @Override
+                                public boolean stopFlashNotificationSequence(
+                                        @androidx.annotation.NonNull Context context) {
+                                    return context.getSystemService(AccessibilityManager.class)
+                                            .stopFlashNotificationSequence(context);
+                                }
+                            },
+                            Executors.newCachedThreadPool(),
+                            new BlockedNumbersAdapter() {
+                                @Override
+                                public boolean shouldShowEmergencyCallNotification(Context
+                                        context) {
+                                    return BlockedNumberContract.SystemContract
+                                            .shouldShowEmergencyCallNotification(context);
+                                }
+
+                                @Override
+                                public void updateEmergencyCallNotification(Context context,
+                                        boolean showNotification) {
+                                    BlockedNumbersUtil.updateEmergencyCallNotification(context,
+                                            showNotification);
+                                }
+                            }));
         }
     }
 
diff --git a/src/com/android/server/telecom/components/UserCallActivity.java b/src/com/android/server/telecom/components/UserCallActivity.java
index 1d85884..d7b2001 100644
--- a/src/com/android/server/telecom/components/UserCallActivity.java
+++ b/src/com/android/server/telecom/components/UserCallActivity.java
@@ -74,7 +74,8 @@
             // ActivityThread.ActivityClientRecord#intent directly.
             // Modifying directly may be a potential risk when relaunching this activity.
             new UserCallIntentProcessor(this, userHandle).processIntent(new Intent(intent),
-                    getCallingPackage(), true /* hasCallAppOp*/, false /* isLocalInvocation */);
+                    getCallingPackage(), false, true /* hasCallAppOp*/,
+                    false /* isLocalInvocation */);
         } finally {
             Log.endSession();
             wakelock.release();
diff --git a/src/com/android/server/telecom/components/UserCallIntentProcessor.java b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
index cad7b4c..8e817fe 100755
--- a/src/com/android/server/telecom/components/UserCallIntentProcessor.java
+++ b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
@@ -69,6 +69,7 @@
      *
      * @param intent The intent.
      * @param callingPackageName The package name of the calling app.
+     * @param isSelfManaged      {@code true} if SelfManaged profile enabled.
      * @param canCallNonEmergency {@code true} if the caller is permitted to call non-emergency
      *                            numbers.
      * @param isLocalInvocation {@code true} if the caller is within the system service (i.e. the
@@ -79,19 +80,21 @@
      *                            service resides.
      */
     public void processIntent(Intent intent, String callingPackageName,
-            boolean canCallNonEmergency, boolean isLocalInvocation) {
+            boolean isSelfManaged, boolean canCallNonEmergency,
+            boolean isLocalInvocation) {
         String action = intent.getAction();
 
         if (Intent.ACTION_CALL.equals(action) ||
                 Intent.ACTION_CALL_PRIVILEGED.equals(action) ||
                 Intent.ACTION_CALL_EMERGENCY.equals(action)) {
-            processOutgoingCallIntent(intent, callingPackageName, canCallNonEmergency,
-                    isLocalInvocation);
+            processOutgoingCallIntent(intent, callingPackageName, isSelfManaged,
+                    canCallNonEmergency, isLocalInvocation);
         }
     }
 
     private void processOutgoingCallIntent(Intent intent, String callingPackageName,
-            boolean canCallNonEmergency, boolean isLocalInvocation) {
+            boolean isSelfManaged, boolean canCallNonEmergency,
+            boolean isLocalInvocation) {
         Uri handle = intent.getData();
         if (handle == null) return;
         String scheme = handle.getScheme();
@@ -102,40 +105,43 @@
             handle = Uri.fromParts(PhoneAccount.SCHEME_SIP, uriString, null);
         }
 
-        // Check DISALLOW_OUTGOING_CALLS restriction. Note: We are skipping this check in a managed
-        // profile user because this check can always be bypassed by copying and pasting the phone
-        // number into the personal dialer.
-        if (!UserUtil.isManagedProfile(mContext, mUserHandle)) {
-            // Only emergency calls are allowed for users with the DISALLOW_OUTGOING_CALLS
-            // restriction.
-            if (!TelephonyUtil.shouldProcessAsEmergency(mContext, handle)) {
-                final UserManager userManager = (UserManager) mContext.getSystemService(
-                        Context.USER_SERVICE);
-                if (userManager.hasBaseUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
-                        mUserHandle)) {
-                    showErrorDialogForRestrictedOutgoingCall(mContext,
-                            R.string.outgoing_call_not_allowed_user_restriction);
-                    Log.w(this, "Rejecting non-emergency phone call due to DISALLOW_OUTGOING_CALLS "
-                            + "restriction");
-                    return;
-                } else if (userManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
-                        mUserHandle)) {
-                    final DevicePolicyManager dpm =
-                            mContext.getSystemService(DevicePolicyManager.class);
-                    if (dpm == null) {
+       if(!isSelfManaged && !isLocalInvocation) {
+            // Check DISALLOW_OUTGOING_CALLS restriction. Note: We are skipping this
+            // check in a managed profile user because this check can always be bypassed
+            // by copying and pasting the phone number into the personal dialer.
+            if (!UserUtil.isManagedProfile(mContext, mUserHandle)) {
+                // Only emergency calls are allowed for users with the DISALLOW_OUTGOING_CALLS
+                // restriction.
+                if (!TelephonyUtil.shouldProcessAsEmergency(mContext, handle)) {
+                    final UserManager userManager =
+                            (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+                    if (userManager.hasBaseUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
+                            mUserHandle)) {
+                        showErrorDialogForRestrictedOutgoingCall(mContext,
+                                R.string.outgoing_call_not_allowed_user_restriction);
+                        Log.w(this, "Rejecting non-emergency phone call "
+                                + "due to DISALLOW_OUTGOING_CALLS restriction");
+                        return;
+                    } else if (userManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
+                            mUserHandle)) {
+                        final DevicePolicyManager dpm =
+                                mContext.getSystemService(DevicePolicyManager.class);
+                        if (dpm == null) {
+                            return;
+                        }
+                        final Intent adminSupportIntent = dpm.createAdminSupportIntent(
+                                UserManager.DISALLOW_OUTGOING_CALLS);
+                        if (adminSupportIntent != null) {
+                            mContext.startActivity(adminSupportIntent);
+                        }
                         return;
                     }
-                    final Intent adminSupportIntent = dpm.createAdminSupportIntent(
-                            UserManager.DISALLOW_OUTGOING_CALLS);
-                    if (adminSupportIntent != null) {
-                        mContext.startActivity(adminSupportIntent);
-                    }
-                    return;
                 }
             }
         }
 
-        if (!canCallNonEmergency && !TelephonyUtil.shouldProcessAsEmergency(mContext, handle)) {
+        if (!isSelfManaged && !canCallNonEmergency &&
+                !TelephonyUtil.shouldProcessAsEmergency(mContext, handle)) {
             showErrorDialogForRestrictedOutgoingCall(mContext,
                     R.string.outgoing_call_not_allowed_no_permission);
             Log.w(this, "Rejecting non-emergency phone call because "
diff --git a/src/com/android/server/telecom/settings/BlockedNumbersActivity.java b/src/com/android/server/telecom/settings/BlockedNumbersActivity.java
index bc54e11..7345a67 100644
--- a/src/com/android/server/telecom/settings/BlockedNumbersActivity.java
+++ b/src/com/android/server/telecom/settings/BlockedNumbersActivity.java
@@ -40,7 +40,6 @@
 import android.text.Editable;
 import android.text.TextUtils;
 import android.text.TextWatcher;
-import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.MenuItem;
 import android.view.View;
@@ -101,6 +100,8 @@
         ActionBar actionBar = getActionBar();
         if (actionBar != null) {
             actionBar.setDisplayHomeAsUpEnabled(true);
+            // set the talkback voice prompt to "Back" instead of "Navigate Up"
+            actionBar.setHomeActionContentDescription(R.string.back);
         }
 
         if (!BlockedNumberContract.canCurrentUserBlockNumbers(this)) {
@@ -154,7 +155,8 @@
             }
         };
         registerReceiver(mBlockingStatusReceiver, new IntentFilter(
-                BlockedNumberContract.SystemContract.ACTION_BLOCK_SUPPRESSION_STATE_CHANGED));
+                BlockedNumberContract.SystemContract.ACTION_BLOCK_SUPPRESSION_STATE_CHANGED),
+                Context.RECEIVER_EXPORTED);
 
         getLoaderManager().initLoader(0, null, this);
     }
diff --git a/src/com/android/server/telecom/stats/CallFailureCause.java b/src/com/android/server/telecom/stats/CallFailureCause.java
new file mode 100644
index 0000000..3eb2321
--- /dev/null
+++ b/src/com/android/server/telecom/stats/CallFailureCause.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2022 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.server.telecom.stats;
+
+/**
+ * Indicating the failure reason why a new call cannot be made.
+ * The codes are synced with CallFailureCauseEnum defined in enums.proto.
+ */
+public enum CallFailureCause {
+    /** The call is normally started. */
+    NONE(0),
+    /** Necessary parameters are invalid or null. */
+    INVALID_USE(1),
+    /** There is an emergency call ongoing. */
+    IN_EMERGENCY_CALL(2),
+    /** There is an live call that cannot be held. */
+    CANNOT_HOLD_CALL(3),
+    /** There are maximum number of outgoing calls already. */
+    MAX_OUTGOING_CALLS(4),
+    /** There are maximum number of ringing calls already. */
+    MAX_RINGING_CALLS(5),
+    /** There are maximum number of calls in hold already. */
+    MAX_HOLD_CALLS(6),
+    /* There are maximum number of self-managed calls already. */
+    MAX_SELF_MANAGED_CALLS(7);
+
+    private final int mCode;
+
+    /**
+     * Creates a new CallFailureCause.
+     *
+     * @param code The code for the failure cause.
+     */
+    CallFailureCause(int code) {
+        mCode = code;
+    }
+
+    /**
+     * Returns the code for the failure.
+     *
+     * @return The code for the failure cause.
+     */
+    public int getCode() {
+        return mCode;
+    }
+
+    /**
+     * Check if this enum represents a non-failure case.
+     *
+     * @return True if success.
+     */
+    public boolean isSuccess() {
+        return this == NONE;
+    }
+}
diff --git a/src/com/android/server/telecom/stats/CallStateChangedAtomWriter.java b/src/com/android/server/telecom/stats/CallStateChangedAtomWriter.java
new file mode 100644
index 0000000..55b002e
--- /dev/null
+++ b/src/com/android/server/telecom/stats/CallStateChangedAtomWriter.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2022 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.server.telecom.stats;
+
+import android.content.pm.PackageManager;
+import android.telecom.DisconnectCause;
+import android.telecom.Log;
+
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.TelecomStatsLog;
+
+/**
+ * Collects and stores data for CallStateChanged atom for each call, and provide a
+ * method to write the data to statsd whenever the call state changes.
+ */
+public class CallStateChangedAtomWriter {
+    private boolean mIsSelfManaged = false;
+    private boolean mIsExternalCall = false;
+    private boolean mIsEmergencyCall = false;
+    private int mUid = -1;
+    private int mDurationSeconds = 0;
+    private int mExistingCallCount = 0;
+    private int mHeldCallCount = 0;
+    private CallFailureCause mStartFailCause = CallFailureCause.NONE;
+    private DisconnectCause mDisconnectCause = new DisconnectCause(DisconnectCause.UNKNOWN);
+
+    /**
+     * Write collected data and current call state to statsd.
+     *
+     * @param state Current call state.
+     */
+    public void write(int state) {
+        TelecomStatsLog.write(TelecomStatsLog.CALL_STATE_CHANGED,
+                state,
+                state == CallState.DISCONNECTED ?
+                    mDisconnectCause.getCode() : DisconnectCause.UNKNOWN,
+                mIsSelfManaged,
+                mIsExternalCall,
+                mIsEmergencyCall,
+                mUid,
+                state == CallState.DISCONNECTED ? mDurationSeconds : 0,
+                mExistingCallCount,
+                mHeldCallCount,
+                state == CallState.DISCONNECTED ?
+                    mStartFailCause.getCode() : CallFailureCause.NONE.getCode());
+    }
+
+    public CallStateChangedAtomWriter setSelfManaged(boolean isSelfManaged) {
+        mIsSelfManaged = isSelfManaged;
+        return this;
+    }
+
+    public CallStateChangedAtomWriter setExternalCall(boolean isExternalCall) {
+        mIsExternalCall = isExternalCall;
+        return this;
+    }
+
+    public CallStateChangedAtomWriter setEmergencyCall(boolean isEmergencyCall) {
+        mIsEmergencyCall = isEmergencyCall;
+        return this;
+    }
+
+    public CallStateChangedAtomWriter setUid(int uid) {
+        mUid = uid;
+        return this;
+    }
+
+    public CallStateChangedAtomWriter setUid(String packageName, PackageManager pm) {
+        try {
+            final int uid = pm.getPackageUid(packageName, PackageManager.PackageInfoFlags.of(0));
+            return setUid(uid);
+
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(this, e, "Could not find the package");
+        }
+        return setUid(-1);
+    }
+
+    public CallStateChangedAtomWriter setDurationSeconds(int duration) {
+        if (duration >= 0) {
+            mDurationSeconds = duration;
+        }
+        return this;
+    }
+
+    public CallStateChangedAtomWriter setExistingCallCount(int count) {
+        mExistingCallCount = count;
+        return this;
+    }
+
+    public CallStateChangedAtomWriter increaseHeldCallCount() {
+        mHeldCallCount++;
+        return this;
+    }
+
+    public CallStateChangedAtomWriter setDisconnectCause(DisconnectCause cause) {
+        mDisconnectCause = cause;
+        return this;
+    }
+
+    public CallStateChangedAtomWriter setStartFailCause(CallFailureCause cause) {
+        mStartFailCause = cause;
+        return this;
+    }
+}
diff --git a/src/com/android/server/telecom/ui/AudioProcessingNotification.java b/src/com/android/server/telecom/ui/AudioProcessingNotification.java
index 7a61460..e38178e 100644
--- a/src/com/android/server/telecom/ui/AudioProcessingNotification.java
+++ b/src/com/android/server/telecom/ui/AudioProcessingNotification.java
@@ -19,6 +19,7 @@
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.content.Context;
+import android.os.UserHandle;
 import android.telecom.Log;
 
 import com.android.server.telecom.Call;
@@ -52,7 +53,8 @@
             showAudioProcessingNotification(call);
         } else if (oldState == CallState.AUDIO_PROCESSING
                 && newState != CallState.AUDIO_PROCESSING) {
-            cancelAudioProcessingNotification();
+            cancelAudioProcessingNotification(
+                    call.getUserHandleFromTargetPhoneAccount());
         }
     }
 
@@ -66,7 +68,8 @@
     @Override
     public void onCallRemoved(Call call) {
         if (call == mCallInAudioProcessing) {
-            cancelAudioProcessingNotification();
+            cancelAudioProcessingNotification(
+                    call.getUserHandleFromTargetPhoneAccount());
         }
     }
 
@@ -76,7 +79,8 @@
      * @param call The missed call.
      */
     private void showAudioProcessingNotification(Call call) {
-        Log.i(this, "showAudioProcessingNotification");
+        Log.i(this, "showAudioProcessingNotification for user = %s",
+                call.getUserHandleFromTargetPhoneAccount());
         mCallInAudioProcessing = call;
 
         Notification.Builder builder = new Notification.Builder(mContext,
@@ -92,12 +96,14 @@
 
         Notification notification = builder.build();
 
-        mNotificationManager.notify(
-                NOTIFICATION_TAG, AUDIO_PROCESSING_NOTIFICATION_ID, notification);
+        mNotificationManager.notifyAsUser(NOTIFICATION_TAG, AUDIO_PROCESSING_NOTIFICATION_ID,
+                notification, mCallInAudioProcessing.getUserHandleFromTargetPhoneAccount());
     }
 
     /** Cancels the audio processing notification. */
-    private void cancelAudioProcessingNotification() {
-        mNotificationManager.cancel(NOTIFICATION_TAG, AUDIO_PROCESSING_NOTIFICATION_ID);
+    private void cancelAudioProcessingNotification(UserHandle userHandle) {
+        Log.i(this, "cancelAudioProcessingNotification for user = %s", userHandle);
+        mNotificationManager.cancelAsUser(NOTIFICATION_TAG,
+                AUDIO_PROCESSING_NOTIFICATION_ID, userHandle);
     }
 }
diff --git a/src/com/android/server/telecom/ui/IncomingCallNotifier.java b/src/com/android/server/telecom/ui/IncomingCallNotifier.java
index 0c1c5a3..3b188d4 100644
--- a/src/com/android/server/telecom/ui/IncomingCallNotifier.java
+++ b/src/com/android/server/telecom/ui/IncomingCallNotifier.java
@@ -22,6 +22,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
+import android.os.UserHandle;
 import android.telecom.Log;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
@@ -166,22 +167,26 @@
                 showIncomingCallNotification(mIncomingCall);
             } else if (hadIncomingCall && !hasIncomingCall) {
                 previousIncomingCall.removeListener(mCallListener);
-                hideIncomingCallNotification();
+                hideIncomingCallNotification(
+                        previousIncomingCall.getUserHandleFromTargetPhoneAccount());
             }
         }
     }
 
     private void showIncomingCallNotification(Call call) {
-        Log.i(this, "showIncomingCallNotification showCall = %s", call);
+        Log.i(this, "showIncomingCallNotification showCall = %s for user = %s",
+                call, call.getUserHandleFromTargetPhoneAccount());
 
         Notification.Builder builder = getNotificationBuilder(call,
                 mCallsManagerProxy.getActiveCall());
-        mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_INCOMING_CALL, builder.build());
+        mNotificationManager.notifyAsUser(NOTIFICATION_TAG, NOTIFICATION_INCOMING_CALL,
+                builder.build(), call.getUserHandleFromTargetPhoneAccount());
     }
 
-    private void hideIncomingCallNotification() {
-        Log.i(this, "hideIncomingCallNotification");
-        mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_INCOMING_CALL);
+    private void hideIncomingCallNotification(UserHandle userHandle) {
+        Log.i(this, "hideIncomingCallNotification for user = %s", userHandle);
+        mNotificationManager.cancelAsUser(NOTIFICATION_TAG, NOTIFICATION_INCOMING_CALL,
+                userHandle);
     }
 
     private String getNotificationName(Call call) {
diff --git a/src/com/android/server/telecom/voip/AnswerCallTransaction.java b/src/com/android/server/telecom/voip/AnswerCallTransaction.java
new file mode 100644
index 0000000..efd2343
--- /dev/null
+++ b/src/com/android/server/telecom/voip/AnswerCallTransaction.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 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.server.telecom.voip;
+
+import android.os.OutcomeReceiver;
+import android.telecom.CallException;
+import android.util.Log;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.CallsManager;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+
+/**
+ * This transaction should be created for new incoming calls that request to go from
+ * CallState.Ringing to CallState.Answered.  Before changing the CallState, the focus manager must
+ * be updated. Once the focus manager updates, the call state will be set.  If there is an issue
+ * answering the call, the transaction will fail.
+ */
+public class AnswerCallTransaction extends VoipCallTransaction {
+
+    private static final String TAG = AnswerCallTransaction.class.getSimpleName();
+    private final CallsManager mCallsManager;
+    private final Call mCall;
+    private final int mVideoState;
+
+    public AnswerCallTransaction(CallsManager callsManager, Call call, int videoState) {
+        super(callsManager.getLock());
+        mCallsManager = callsManager;
+        mCall = call;
+        mVideoState = videoState;
+    }
+
+    @Override
+    public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+        Log.d(TAG, "processTransaction");
+        CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
+
+        mCall.setVideoState(mVideoState);
+
+        mCallsManager.transactionRequestNewFocusCall(mCall, CallState.ANSWERED,
+                new OutcomeReceiver<>() {
+            @Override
+            public void onResult(Boolean result) {
+                Log.d(TAG, "processTransaction: onResult");
+                future.complete(new VoipCallTransactionResult(
+                        VoipCallTransactionResult.RESULT_SUCCEED, null));
+            }
+
+            @Override
+            public void onError(CallException exception) {
+                Log.d(TAG, "processTransaction: onError");
+                future.complete(new VoipCallTransactionResult(
+                        exception.getCode(), exception.getMessage()));
+            }
+        });
+
+        return future;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/voip/CallEventCallbackAckTransaction.java b/src/com/android/server/telecom/voip/CallEventCallbackAckTransaction.java
new file mode 100644
index 0000000..3d59ed3
--- /dev/null
+++ b/src/com/android/server/telecom/voip/CallEventCallbackAckTransaction.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2022 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.server.telecom.voip;
+
+import static android.telecom.TelecomManager.TELECOM_TRANSACTION_SUCCESS;
+import static android.telecom.CallException.CODE_OPERATION_TIMED_OUT;
+
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.telecom.DisconnectCause;
+import android.util.Log;
+
+import com.android.internal.telecom.ICallEventCallback;
+import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.TransactionalServiceWrapper;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * SRP: using the ICallEventCallback binder, reach out to the client for the pending call event and
+ * get an acknowledgement that the call event can be completed.
+ */
+public class CallEventCallbackAckTransaction extends VoipCallTransaction {
+    private static final String TAG = CallEventCallbackAckTransaction.class.getSimpleName();
+    private final ICallEventCallback mICallEventCallback;
+    private final String mAction;
+    private final String mCallId;
+    // optional values
+    private int mVideoState = 0;
+    private DisconnectCause mDisconnectCause = null;
+
+    private final VoipCallTransactionResult TRANSACTION_FAILED = new VoipCallTransactionResult(
+            CODE_OPERATION_TIMED_OUT, "failed to complete the operation before timeout");
+
+    private static class AckResultReceiver extends ResultReceiver {
+        CountDownLatch mCountDownLatch;
+
+        public AckResultReceiver(CountDownLatch latch) {
+            super(null);
+            mCountDownLatch = latch;
+        }
+
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            if (resultCode == TELECOM_TRANSACTION_SUCCESS) {
+                mCountDownLatch.countDown();
+            }
+        }
+    }
+
+    public CallEventCallbackAckTransaction(ICallEventCallback service, String action,
+            String callId, TelecomSystem.SyncRoot lock) {
+        super(lock);
+        mICallEventCallback = service;
+        mAction = action;
+        mCallId = callId;
+    }
+
+
+    public CallEventCallbackAckTransaction(ICallEventCallback service, String action, String callId,
+            int videoState, TelecomSystem.SyncRoot lock) {
+        super(lock);
+        mICallEventCallback = service;
+        mAction = action;
+        mCallId = callId;
+        mVideoState = videoState;
+    }
+
+    public CallEventCallbackAckTransaction(ICallEventCallback service, String action, String callId,
+            DisconnectCause cause, TelecomSystem.SyncRoot lock) {
+        super(lock);
+        mICallEventCallback = service;
+        mAction = action;
+        mCallId = callId;
+        mDisconnectCause = cause;
+    }
+
+
+    @Override
+    public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+        Log.d(TAG, "processTransaction");
+        CountDownLatch latch = new CountDownLatch(1);
+        ResultReceiver receiver = new AckResultReceiver(latch);
+
+        try {
+            switch (mAction) {
+                case TransactionalServiceWrapper.ON_SET_INACTIVE:
+                    mICallEventCallback.onSetInactive(mCallId, receiver);
+                    break;
+                case TransactionalServiceWrapper.ON_DISCONNECT:
+                    mICallEventCallback.onDisconnect(mCallId, mDisconnectCause, receiver);
+                    break;
+                case TransactionalServiceWrapper.ON_SET_ACTIVE:
+                    mICallEventCallback.onSetActive(mCallId, receiver);
+                    break;
+                case TransactionalServiceWrapper.ON_ANSWER:
+                    mICallEventCallback.onAnswer(mCallId, mVideoState, receiver);
+                    break;
+                case TransactionalServiceWrapper.ON_STREAMING_STARTED:
+                    mICallEventCallback.onCallStreamingStarted(mCallId, receiver);
+                    break;
+            }
+        } catch (RemoteException remoteException) {
+            return CompletableFuture.completedFuture(TRANSACTION_FAILED);
+        }
+
+        try {
+            // wait for the client to ack that CallEventCallback
+            boolean success = latch.await(VoipCallTransaction.TIMEOUT_LIMIT, TimeUnit.MILLISECONDS);
+            if (!success) {
+                // client send onError and failed to complete transaction
+                return CompletableFuture.completedFuture(TRANSACTION_FAILED);
+            } else {
+                // success
+                return CompletableFuture.completedFuture(
+                        new VoipCallTransactionResult(VoipCallTransactionResult.RESULT_SUCCEED,
+                                "success"));
+            }
+        } catch (InterruptedException ie) {
+            return CompletableFuture.completedFuture(TRANSACTION_FAILED);
+        }
+    }
+}
diff --git a/src/com/android/server/telecom/voip/EndCallTransaction.java b/src/com/android/server/telecom/voip/EndCallTransaction.java
new file mode 100644
index 0000000..0cb7458
--- /dev/null
+++ b/src/com/android/server/telecom/voip/EndCallTransaction.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 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.server.telecom.voip;
+
+import android.telecom.DisconnectCause;
+import android.util.Log;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.CallsManager;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+
+/**
+ * This transaction should only be created for a CallControl action.
+ */
+public class EndCallTransaction extends VoipCallTransaction {
+    private static final String TAG = EndCallTransaction.class.getSimpleName();
+    private final CallsManager mCallsManager;
+    private final Call mCall;
+    private DisconnectCause mCause;
+
+    public EndCallTransaction(CallsManager callsManager, DisconnectCause cause, Call call) {
+        super(callsManager.getLock());
+        mCallsManager = callsManager;
+        mCause = cause;
+        mCall = call;
+    }
+
+    @Override
+    public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+        int code = mCause.getCode();
+        Log.d(TAG, String.format("processTransaction: mCode=[%d], mCall=[%s]", code, mCall));
+
+        if (mCall.getState() == CallState.RINGING && code == DisconnectCause.LOCAL) {
+            mCause = new DisconnectCause(DisconnectCause.REJECTED,
+                    "overrode cause in EndCallTransaction");
+        }
+
+        mCallsManager.markCallAsDisconnected(mCall, mCause);
+        mCallsManager.markCallAsRemoved(mCall);
+
+        return CompletableFuture.completedFuture(
+                new VoipCallTransactionResult(VoipCallTransactionResult.RESULT_SUCCEED,
+                        "EndCallTransaction: RESULT_SUCCEED"));
+    }
+}
diff --git a/src/com/android/server/telecom/voip/EndpointChangeTransaction.java b/src/com/android/server/telecom/voip/EndpointChangeTransaction.java
new file mode 100644
index 0000000..e037a79
--- /dev/null
+++ b/src/com/android/server/telecom/voip/EndpointChangeTransaction.java
@@ -0,0 +1,59 @@
+
+/*
+ * Copyright (C) 2022 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.server.telecom.voip;
+
+import android.os.Bundle;
+import android.os.ResultReceiver;
+import android.telecom.CallEndpoint;
+import android.util.Log;
+
+import com.android.server.telecom.CallsManager;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+
+public class EndpointChangeTransaction extends VoipCallTransaction {
+    private static final String TAG = EndpointChangeTransaction.class.getSimpleName();
+    private final CallEndpoint mCallEndpoint;
+    private final CallsManager mCallsManager;
+
+    public EndpointChangeTransaction(CallEndpoint endpoint, CallsManager callsManager) {
+        super(callsManager.getLock());
+        mCallEndpoint = endpoint;
+        mCallsManager = callsManager;
+    }
+
+    @Override
+    public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+        Log.i(TAG, "processTransaction");
+        CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
+        mCallsManager.requestCallEndpointChange(mCallEndpoint, new ResultReceiver(null) {
+            @Override
+            protected void onReceiveResult(int resultCode, Bundle resultData) {
+                Log.i(TAG, "processTransaction: code=" + resultCode);
+                if (resultCode == CallEndpoint.ENDPOINT_OPERATION_SUCCESS) {
+                    future.complete(new VoipCallTransactionResult(
+                            VoipCallTransactionResult.RESULT_SUCCEED, null));
+                } else {
+                    future.complete(new VoipCallTransactionResult(
+                            VoipCallTransactionResult.RESULT_FAILED, null));
+                }
+            }
+        });
+        return future;
+    }
+}
diff --git a/src/com/android/server/telecom/voip/HoldActiveCallForNewCallTransaction.java b/src/com/android/server/telecom/voip/HoldActiveCallForNewCallTransaction.java
new file mode 100644
index 0000000..ab203ad
--- /dev/null
+++ b/src/com/android/server/telecom/voip/HoldActiveCallForNewCallTransaction.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2022 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.server.telecom.voip;
+
+import android.os.OutcomeReceiver;
+import android.telecom.CallException;
+import android.util.Log;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallsManager;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+
+public class HoldActiveCallForNewCallTransaction extends VoipCallTransaction {
+
+    private static final String TAG = HoldActiveCallForNewCallTransaction.class.getSimpleName();
+    private final CallsManager mCallsManager;
+    private final Call mCall;
+
+    public HoldActiveCallForNewCallTransaction(CallsManager callsManager, Call call) {
+        super(callsManager.getLock());
+        mCallsManager = callsManager;
+        mCall = call;
+    }
+
+    @Override
+    public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+        Log.d(TAG, "processTransaction");
+        CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
+
+        mCallsManager.transactionHoldPotentialActiveCallForNewCall(mCall, new OutcomeReceiver<>() {
+            @Override
+            public void onResult(Boolean result) {
+                Log.d(TAG, "processTransaction: onResult");
+                future.complete(new VoipCallTransactionResult(
+                        VoipCallTransactionResult.RESULT_SUCCEED, null));
+            }
+
+            @Override
+            public void onError(CallException exception) {
+                Log.d(TAG, "processTransaction: onError");
+                future.complete(new VoipCallTransactionResult(
+                       exception.getCode(), exception.getMessage()));
+            }
+        });
+
+        return future;
+    }
+}
diff --git a/src/com/android/server/telecom/voip/HoldCallTransaction.java b/src/com/android/server/telecom/voip/HoldCallTransaction.java
new file mode 100644
index 0000000..6c4e8b7
--- /dev/null
+++ b/src/com/android/server/telecom/voip/HoldCallTransaction.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 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.server.telecom.voip;
+
+import android.telecom.CallException;
+import android.util.Log;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallsManager;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+
+public class HoldCallTransaction extends VoipCallTransaction {
+
+    private static final String TAG = HoldCallTransaction.class.getSimpleName();
+    private final CallsManager mCallsManager;
+    private final Call mCall;
+
+    public HoldCallTransaction(CallsManager callsManager, Call call) {
+        super(callsManager.getLock());
+        mCallsManager = callsManager;
+        mCall = call;
+    }
+
+    @Override
+    public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+        Log.d(TAG, "processTransaction");
+        CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
+
+        if (mCallsManager.canHold(mCall)) {
+            mCallsManager.markCallAsOnHold(mCall);
+            future.complete(new VoipCallTransactionResult(
+                    VoipCallTransactionResult.RESULT_SUCCEED, null));
+        } else {
+            Log.d(TAG, "processTransaction: onError");
+            future.complete(new VoipCallTransactionResult(
+                    CallException.CODE_CANNOT_HOLD_CURRENT_ACTIVE_CALL, "cannot hold call"));
+        }
+        return future;
+    }
+}
diff --git a/src/com/android/server/telecom/voip/IncomingCallTransaction.java b/src/com/android/server/telecom/voip/IncomingCallTransaction.java
new file mode 100644
index 0000000..c0bb93d
--- /dev/null
+++ b/src/com/android/server/telecom/voip/IncomingCallTransaction.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2022 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.server.telecom.voip;
+
+import static android.telecom.CallAttributes.CALL_CAPABILITIES_KEY;
+
+import android.os.Bundle;
+import android.telecom.CallAttributes;
+import android.telecom.CallException;
+import android.telecom.TelecomManager;
+import android.util.Log;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallsManager;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+
+public class IncomingCallTransaction extends VoipCallTransaction {
+
+    private static final String TAG = IncomingCallTransaction.class.getSimpleName();
+    private final String mCallId;
+    private final CallAttributes mCallAttributes;
+    private final CallsManager mCallsManager;
+    private final Bundle mExtras;
+
+    public IncomingCallTransaction(String callId, CallAttributes callAttributes,
+            CallsManager callsManager, Bundle extras) {
+        super(callsManager.getLock());
+        mExtras = extras;
+        mCallId = callId;
+        mCallAttributes = callAttributes;
+        mCallsManager = callsManager;
+    }
+
+    public IncomingCallTransaction(String callId, CallAttributes callAttributes,
+            CallsManager callsManager) {
+        this(callId, callAttributes, callsManager, new Bundle());
+    }
+
+    @Override
+    public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+        Log.d(TAG, "processTransaction");
+
+        if (mCallsManager.isIncomingCallPermitted(mCallAttributes.getPhoneAccountHandle())) {
+            Log.d(TAG, "processTransaction: incoming call permitted");
+
+            Call call = mCallsManager.processIncomingCallIntent(
+                    mCallAttributes.getPhoneAccountHandle(),
+                    generateExtras(mCallAttributes), false);
+
+            return CompletableFuture.completedFuture(
+                    new VoipCallTransactionResult(
+                            VoipCallTransactionResult.RESULT_SUCCEED, call, "success"));
+        } else {
+            Log.d(TAG, "processTransaction: incoming call is not permitted at this time");
+
+            return CompletableFuture.completedFuture(
+                    new VoipCallTransactionResult(
+                            CallException.CODE_CALL_NOT_PERMITTED_AT_PRESENT_TIME,
+                            "incoming call not permitted at the current time"));
+        }
+    }
+
+    private Bundle generateExtras(CallAttributes callAttributes) {
+        mExtras.putString(TelecomManager.TRANSACTION_CALL_ID_KEY, mCallId);
+        mExtras.putInt(CALL_CAPABILITIES_KEY, callAttributes.getCallCapabilities());
+        mExtras.putInt(TelecomManager.EXTRA_INCOMING_VIDEO_STATE, callAttributes.getCallType());
+        return mExtras;
+    }
+}
diff --git a/src/com/android/server/telecom/voip/OutgoingCallTransaction.java b/src/com/android/server/telecom/voip/OutgoingCallTransaction.java
new file mode 100644
index 0000000..0b17da2
--- /dev/null
+++ b/src/com/android/server/telecom/voip/OutgoingCallTransaction.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2022 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.server.telecom.voip;
+
+import static android.Manifest.permission.CALL_PRIVILEGED;
+import static android.telecom.CallAttributes.CALL_CAPABILITIES_KEY;
+import static android.telecom.CallException.CODE_CALL_NOT_PERMITTED_AT_PRESENT_TIME;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.telecom.CallAttributes;
+import android.telecom.TelecomManager;
+import android.util.Log;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.LoggedHandlerExecutor;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+
+public class OutgoingCallTransaction extends VoipCallTransaction {
+
+    private static final String TAG = OutgoingCallTransaction.class.getSimpleName();
+    private final String mCallId;
+    private final Context mContext;
+    private final String mCallingPackage;
+    private final CallAttributes mCallAttributes;
+    private final CallsManager mCallsManager;
+    private final Bundle mExtras;
+
+    public OutgoingCallTransaction(String callId, Context context, CallAttributes callAttributes,
+            CallsManager callsManager, Bundle extras) {
+        super(callsManager.getLock());
+        mCallId = callId;
+        mContext = context;
+        mCallAttributes = callAttributes;
+        mCallsManager = callsManager;
+        mExtras = extras;
+        mCallingPackage = mContext.getOpPackageName();
+    }
+
+    public OutgoingCallTransaction(String callId, Context context, CallAttributes callAttributes,
+            CallsManager callsManager) {
+        this(callId, context, callAttributes, callsManager, new Bundle());
+    }
+
+    @Override
+    public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+        Log.d(TAG, "processTransaction");
+
+        final boolean hasCallPrivilegedPermission = mContext.checkCallingPermission(
+                CALL_PRIVILEGED) == PackageManager.PERMISSION_GRANTED;
+
+        final Intent intent = new Intent(hasCallPrivilegedPermission ?
+                Intent.ACTION_CALL_PRIVILEGED : Intent.ACTION_CALL, mCallAttributes.getAddress());
+
+        if (mCallsManager.isOutgoingCallPermitted(mCallAttributes.getPhoneAccountHandle())) {
+            Log.d(TAG, "processTransaction: outgoing call permitted");
+
+            CompletableFuture<Call> callFuture =
+                    mCallsManager.startOutgoingCall(mCallAttributes.getAddress(),
+                            mCallAttributes.getPhoneAccountHandle(),
+                            generateExtras(mCallAttributes),
+                            mCallAttributes.getPhoneAccountHandle().getUserHandle(),
+                            intent,
+                            mCallingPackage);
+
+            if (callFuture == null) {
+                return CompletableFuture.completedFuture(
+                        new VoipCallTransactionResult(
+                                CODE_CALL_NOT_PERMITTED_AT_PRESENT_TIME,
+                                "incoming call not permitted at the current time"));
+            }
+            CompletionStage<VoipCallTransactionResult> result = callFuture.thenComposeAsync(
+                    (call) -> {
+
+                        Log.d(TAG, "processTransaction: completing future");
+
+                        if (call == null) {
+                            Log.d(TAG, "processTransaction: call is null");
+                            return CompletableFuture.completedFuture(
+                                    new VoipCallTransactionResult(
+                                            CODE_CALL_NOT_PERMITTED_AT_PRESENT_TIME,
+                                            "call could not be created at this time"));
+                        } else {
+                            Log.d(TAG, "processTransaction: call done. id=" + call.getId());
+                        }
+
+                        return CompletableFuture.completedFuture(
+                                new VoipCallTransactionResult(
+                                        VoipCallTransactionResult.RESULT_SUCCEED,
+                                        call, null));
+                    }
+                    , new LoggedHandlerExecutor(mHandler, "OCT.pT", null));
+
+            return result;
+        } else {
+            return CompletableFuture.completedFuture(
+                    new VoipCallTransactionResult(
+                            CODE_CALL_NOT_PERMITTED_AT_PRESENT_TIME,
+                            "incoming call not permitted at the current time"));
+
+        }
+    }
+
+    private Bundle generateExtras(CallAttributes callAttributes) {
+        mExtras.setDefusable(true);
+        mExtras.putString(TelecomManager.TRANSACTION_CALL_ID_KEY, mCallId);
+        mExtras.putInt(CALL_CAPABILITIES_KEY, callAttributes.getCallCapabilities());
+        mExtras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
+                callAttributes.getCallType());
+        return mExtras;
+    }
+}
diff --git a/src/com/android/server/telecom/voip/ParallelTransaction.java b/src/com/android/server/telecom/voip/ParallelTransaction.java
new file mode 100644
index 0000000..2762949
--- /dev/null
+++ b/src/com/android/server/telecom/voip/ParallelTransaction.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2022 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.server.telecom.voip;
+
+import com.android.server.telecom.TelecomSystem;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * A VoipCallTransaction implementation that its sub transactions will be executed in parallel
+ */
+public class ParallelTransaction extends VoipCallTransaction {
+    public ParallelTransaction(List<VoipCallTransaction> subTransactions,
+            TelecomSystem.SyncRoot lock) {
+        super(subTransactions, lock);
+    }
+
+    @Override
+    public void start() {
+        // post timeout work
+        mHandler.postDelayed(() -> {
+            if (mCompleted.getAndSet(true)) {
+                return;
+            }
+            if (mCompleteListener != null) {
+                mCompleteListener.onTransactionTimeout(mTransactionName);
+            }
+            finish();
+        }, TIMEOUT_LIMIT);
+
+        if (mSubTransactions != null && mSubTransactions.size() > 0) {
+            TransactionManager.TransactionCompleteListener subTransactionListener =
+                    new TransactionManager.TransactionCompleteListener() {
+                        private final AtomicInteger mCount = new AtomicInteger(mSubTransactions.size());
+
+                        @Override
+                        public void onTransactionCompleted(VoipCallTransactionResult result,
+                                String transactionName) {
+                            if (result.getResult() != VoipCallTransactionResult.RESULT_SUCCEED) {
+                                mHandler.post(() -> {
+                                    VoipCallTransactionResult mainResult =
+                                            new VoipCallTransactionResult(
+                                                    VoipCallTransactionResult.RESULT_FAILED,
+                                                    String.format("sub transaction %s failed",
+                                                            transactionName));
+                                    mCompleteListener.onTransactionCompleted(mainResult,
+                                            mTransactionName);
+                                    finish();
+                                });
+                            } else {
+                                if (mCount.decrementAndGet() == 0) {
+                                    scheduleTransaction();
+                                }
+                            }
+                        }
+
+                        @Override
+                        public void onTransactionTimeout(String transactionName) {
+                            mHandler.post(() -> {
+                                VoipCallTransactionResult mainResult = new VoipCallTransactionResult(
+                                        VoipCallTransactionResult.RESULT_FAILED,
+                                        String.format("sub transaction %s timed out",
+                                                transactionName));
+                                mCompleteListener.onTransactionCompleted(mainResult,
+                                        mTransactionName);
+                                finish();
+                            });
+                        }
+                    };
+            for (VoipCallTransaction transaction : mSubTransactions) {
+                transaction.setCompleteListener(subTransactionListener);
+                transaction.start();
+            }
+        } else {
+            scheduleTransaction();
+        }
+    }
+}
diff --git a/src/com/android/server/telecom/voip/RequestFocusTransaction.java b/src/com/android/server/telecom/voip/RequestFocusTransaction.java
new file mode 100644
index 0000000..cb4ee37
--- /dev/null
+++ b/src/com/android/server/telecom/voip/RequestFocusTransaction.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 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.server.telecom.voip;
+
+import android.os.OutcomeReceiver;
+import android.telecom.CallException;
+import android.util.Log;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.CallsManager;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+
+public class RequestFocusTransaction extends VoipCallTransaction {
+
+    private static final String TAG = RequestFocusTransaction.class.getSimpleName();
+    private final CallsManager mCallsManager;
+    private final Call mCall;
+
+    public RequestFocusTransaction(CallsManager callsManager, Call call) {
+        super(callsManager.getLock());
+        mCallsManager = callsManager;
+        mCall = call;
+    }
+
+    @Override
+    public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+        Log.d(TAG, "processTransaction");
+        CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
+
+        mCallsManager.transactionRequestNewFocusCall(mCall, CallState.ACTIVE,
+                new OutcomeReceiver<>() {
+            @Override
+            public void onResult(Boolean result) {
+                Log.d(TAG, "processTransaction: onResult");
+                future.complete(new VoipCallTransactionResult(
+                        VoipCallTransactionResult.RESULT_SUCCEED, null));
+            }
+
+            @Override
+            public void onError(CallException exception) {
+                Log.d(TAG, "processTransaction: onError");
+                future.complete(new VoipCallTransactionResult(
+                        exception.getCode(), exception.getMessage()));
+            }
+        });
+
+        return future;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/voip/SerialTransaction.java b/src/com/android/server/telecom/voip/SerialTransaction.java
new file mode 100644
index 0000000..4c6d02e
--- /dev/null
+++ b/src/com/android/server/telecom/voip/SerialTransaction.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2022 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.server.telecom.voip;
+
+import com.android.server.telecom.TelecomSystem;
+
+import java.util.List;
+
+/**
+ * A VoipCallTransaction implementation that its sub transactions will be executed in serial
+ */
+public class SerialTransaction extends VoipCallTransaction {
+    public SerialTransaction(List<VoipCallTransaction> subTransactions,
+            TelecomSystem.SyncRoot lock) {
+        super(subTransactions, lock);
+    }
+
+    public void appendTransaction(VoipCallTransaction transaction){
+        mSubTransactions.add(transaction);
+    }
+
+    @Override
+    public void start() {
+        // post timeout work
+        mHandler.postDelayed(() -> {
+            if (mCompleted.getAndSet(true)) {
+                return;
+            }
+            if (mCompleteListener != null) {
+                mCompleteListener.onTransactionTimeout(mTransactionName);
+            }
+            finish();
+        }, TIMEOUT_LIMIT);
+
+        if (mSubTransactions != null && mSubTransactions.size() > 0) {
+            TransactionManager.TransactionCompleteListener subTransactionListener =
+                    new TransactionManager.TransactionCompleteListener() {
+
+                        @Override
+                        public void onTransactionCompleted(VoipCallTransactionResult result,
+                                String transactionName) {
+                            if (result.getResult() != VoipCallTransactionResult.RESULT_SUCCEED) {
+                                mHandler.post(() -> {
+                                    VoipCallTransactionResult mainResult =
+                                            new VoipCallTransactionResult(
+                                                    VoipCallTransactionResult.RESULT_FAILED,
+                                                    String.format("sub transaction %s failed",
+                                                            transactionName));
+                                    mCompleteListener.onTransactionCompleted(mainResult,
+                                            mTransactionName);
+                                    finish();
+                                });
+                            } else {
+                                if (mSubTransactions.size() > 0) {
+                                    VoipCallTransaction transaction = mSubTransactions.remove(0);
+                                    transaction.setCompleteListener(this);
+                                    transaction.start();
+                                } else {
+                                    scheduleTransaction();
+                                }
+                            }
+                        }
+
+                        @Override
+                        public void onTransactionTimeout(String transactionName) {
+                            mHandler.post(() -> {
+                                VoipCallTransactionResult mainResult = new VoipCallTransactionResult(
+                                        VoipCallTransactionResult.RESULT_FAILED,
+                                        String.format("sub transaction %s timed out",
+                                                transactionName));
+                                mCompleteListener.onTransactionCompleted(mainResult,
+                                        mTransactionName);
+                                finish();
+                            });
+                        }
+                    };
+            VoipCallTransaction transaction = mSubTransactions.remove(0);
+            transaction.setCompleteListener(subTransactionListener);
+            transaction.start();
+        } else {
+            scheduleTransaction();
+        }
+    }
+}
diff --git a/src/com/android/server/telecom/voip/TransactionManager.java b/src/com/android/server/telecom/voip/TransactionManager.java
new file mode 100644
index 0000000..a0955c8
--- /dev/null
+++ b/src/com/android/server/telecom/voip/TransactionManager.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2022 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.server.telecom.voip;
+
+import android.os.OutcomeReceiver;
+import android.telecom.TelecomManager;
+import android.telecom.CallException;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayDeque;
+import java.util.Queue;
+
+public class TransactionManager {
+    private static final String TAG = "VoipCallTransactionManager";
+    private static TransactionManager INSTANCE = null;
+    private static final Object sLock = new Object();
+    private Queue<VoipCallTransaction> mTransactions;
+    private VoipCallTransaction mCurrentTransaction;
+
+    public interface TransactionCompleteListener {
+        void onTransactionCompleted(VoipCallTransactionResult result, String transactionName);
+        void onTransactionTimeout(String transactionName);
+    }
+
+    private TransactionManager() {
+        mTransactions = new ArrayDeque<>();
+        mCurrentTransaction = null;
+    }
+
+    public static TransactionManager getInstance() {
+        synchronized (sLock) {
+            if (INSTANCE == null) {
+                INSTANCE = new TransactionManager();
+            }
+        }
+        return INSTANCE;
+    }
+
+    @VisibleForTesting
+    public static TransactionManager getTestInstance() {
+        return new TransactionManager();
+    }
+
+    public void addTransaction(VoipCallTransaction transaction,
+            OutcomeReceiver<VoipCallTransactionResult, CallException> receiver) {
+        synchronized (sLock) {
+            mTransactions.add(transaction);
+            transaction.setCompleteListener(new TransactionCompleteListener() {
+                @Override
+                public void onTransactionCompleted(VoipCallTransactionResult result,
+                        String transactionName){
+                    Log.i(TAG, String.format("transaction completed: with result=[%d]",
+                            result.getResult()));
+                    if (result.getResult() == TelecomManager.TELECOM_TRANSACTION_SUCCESS) {
+                        receiver.onResult(result);
+                    } else {
+                        receiver.onError(
+                                new CallException(result.getMessage(),
+                                        result.getResult()));
+                    }
+                    finishTransaction();
+                }
+
+                @Override
+                public void onTransactionTimeout(String transactionName){
+                    receiver.onResult(new VoipCallTransactionResult(
+                            VoipCallTransactionResult.RESULT_FAILED, transactionName + " timeout"));
+                    finishTransaction();
+                }
+            });
+        }
+        startTransactions();
+    }
+
+    private void startTransactions() {
+        synchronized (sLock) {
+            if (mTransactions.isEmpty()) {
+                // No transaction waiting for process
+                return;
+            }
+
+            if (mCurrentTransaction != null) {
+                // Ongoing transaction
+                return;
+            }
+            mCurrentTransaction = mTransactions.poll();
+            mCurrentTransaction.start();
+        }
+    }
+
+    private void finishTransaction() {
+        synchronized (sLock) {
+            mCurrentTransaction = null;
+        }
+        startTransactions();
+    }
+
+    @VisibleForTesting
+    public void clear() {
+        synchronized (sLock) {
+            for (VoipCallTransaction transaction : mTransactions) {
+                transaction.finish();
+            }
+        }
+    }
+}
diff --git a/src/com/android/server/telecom/voip/VoipCallMonitor.java b/src/com/android/server/telecom/voip/VoipCallMonitor.java
new file mode 100644
index 0000000..67af11d
--- /dev/null
+++ b/src/com/android/server/telecom/voip/VoipCallMonitor.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2022 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.server.telecom.voip;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.ForegroundServiceDelegationOptions;
+import android.app.Notification;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.notification.NotificationListenerService;
+import android.service.notification.StatusBarNotification;
+import android.telecom.Log;
+import android.telecom.PhoneAccountHandle;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallsManagerListenerBase;
+import com.android.server.telecom.TelecomSystem;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+public class VoipCallMonitor extends CallsManagerListenerBase {
+
+    private final List<Call> mPendingCalls;
+    private final Map<StatusBarNotification, Call> mNotifications;
+    private final Map<PhoneAccountHandle, Set<Call>> mPhoneAccountHandleListMap;
+    private ActivityManagerInternal mActivityManagerInternal;
+    private final Map<PhoneAccountHandle, ServiceConnection> mServices;
+    private NotificationListenerService mNotificationListener;
+    private final Object mLock = new Object();
+    private final HandlerThread mHandlerThread;
+    private final Handler mHandler;
+    private final Context mContext;
+
+    public VoipCallMonitor(Context context, TelecomSystem.SyncRoot lock) {
+        mContext = context;
+        mHandlerThread = new HandlerThread(this.getClass().getSimpleName());
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+        mPendingCalls = new ArrayList<>();
+        mNotifications = new HashMap<>();
+        mServices = new HashMap<>();
+        mPhoneAccountHandleListMap = new HashMap<>();
+        mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
+
+        mNotificationListener = new NotificationListenerService() {
+            @Override
+            public void onNotificationPosted(StatusBarNotification sbn) {
+                synchronized (mLock) {
+                    if (mPendingCalls.isEmpty()) {
+                        return;
+                    }
+                    if (sbn.getNotification().isStyle(Notification.CallStyle.class)) {
+                        String packageName = sbn.getPackageName();
+                        UserHandle userHandle = sbn.getUser();
+
+                        for (Call call : mPendingCalls) {
+                            if (packageName != null &&
+                                    packageName.equals(call.getTargetPhoneAccount()
+                                            .getComponentName().getPackageName())
+                                    && userHandle != null
+                                    && userHandle.equals(call.getInitiatingUser())) {
+                                mPendingCalls.remove(call);
+                                mNotifications.put(sbn, call);
+                                break;
+                            }
+                        }
+                    }
+                }
+            }
+
+            @Override
+            public void onNotificationRemoved(StatusBarNotification sbn) {
+                synchronized (mLock) {
+                    if (mNotifications.isEmpty()) {
+                        return;
+                    }
+                    Call call = mNotifications.getOrDefault(sbn, null);
+                    if (call != null) {
+                        mNotifications.remove(sbn, call);
+                        stopFGSDelegation(call.getTargetPhoneAccount());
+                    }
+                }
+            }
+        };
+
+    }
+
+    public void startMonitor() {
+        try {
+            mNotificationListener.registerAsSystemService(mContext,
+                    new ComponentName(this.getClass().getPackageName(),
+                            this.getClass().getCanonicalName()), ActivityManager.getCurrentUser());
+        } catch (RemoteException e) {
+            Log.e(this, e, "Cannot register notification listener");
+        }
+    }
+
+    public void stopMonitor() {
+        try {
+            mNotificationListener.unregisterAsSystemService();
+        } catch (RemoteException e) {
+            Log.e(this, e, "Cannot unregister notification listener");
+        }
+    }
+
+    @Override
+    public void onCallAdded(Call call) {
+        if (!call.isTransactionalCall()) {
+            return;
+        }
+
+        synchronized (mLock) {
+            PhoneAccountHandle phoneAccountHandle = call.getTargetPhoneAccount();
+            Set<Call> callList = mPhoneAccountHandleListMap.computeIfAbsent(phoneAccountHandle,
+                    k -> new HashSet<>());
+            callList.add(call);
+
+            mHandler.post(
+                    () -> startFGSDelegation(call.getCallingPackageIdentity().mCallingPackagePid,
+                            call.getCallingPackageIdentity().mCallingPackageUid, call));
+        }
+    }
+
+    @Override
+    public void onCallRemoved(Call call) {
+        if (!call.isTransactionalCall()) {
+            return;
+        }
+
+        synchronized (mLock) {
+            stopMonitorWorks(call);
+            PhoneAccountHandle phoneAccountHandle = call.getTargetPhoneAccount();
+            Set<Call> callList = mPhoneAccountHandleListMap.computeIfAbsent(phoneAccountHandle,
+                    k -> new HashSet<>());
+            callList.remove(call);
+
+            if (callList.isEmpty()) {
+                stopFGSDelegation(phoneAccountHandle);
+            }
+        }
+    }
+
+    private void startFGSDelegation(int pid, int uid, Call call) {
+        Log.i(this, "startFGSDelegation for call %s", call.getId());
+        if (mActivityManagerInternal != null) {
+            PhoneAccountHandle handle = call.getTargetPhoneAccount();
+            ForegroundServiceDelegationOptions options = new ForegroundServiceDelegationOptions(pid,
+                    uid, handle.getComponentName().getPackageName(), null /* clientAppThread */,
+                    false /* isSticky */, String.valueOf(handle.hashCode()),
+                    0 /* foregroundServiceType */,
+                    ForegroundServiceDelegationOptions.DELEGATION_SERVICE_PHONE_CALL);
+            ServiceConnection fgsConnection = new ServiceConnection() {
+                @Override
+                public void onServiceConnected(ComponentName name, IBinder service) {
+                    mServices.put(handle, this);
+                    startMonitorWorks(call);
+                }
+
+                @Override
+                public void onServiceDisconnected(ComponentName name) {
+                    mServices.remove(handle);
+                }
+            };
+            try {
+                mActivityManagerInternal.startForegroundServiceDelegate(options, fgsConnection);
+            } catch (Exception e) {
+                Log.i(this, "startForegroundServiceDelegate failed due to: " + e);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    public void stopFGSDelegation(PhoneAccountHandle handle) {
+        synchronized (mLock) {
+            Log.i(this, "stopFGSDelegation of handle %s", handle);
+            Set<Call> calls = mPhoneAccountHandleListMap.get(handle);
+            for (Call call : calls) {
+                stopMonitorWorks(call);
+            }
+            mPhoneAccountHandleListMap.remove(handle);
+
+            if (mActivityManagerInternal != null) {
+                ServiceConnection fgsConnection = mServices.get(handle);
+                if (fgsConnection != null) {
+                    mActivityManagerInternal.stopForegroundServiceDelegate(fgsConnection);
+                }
+            }
+        }
+    }
+
+    private void startMonitorWorks(Call call) {
+        startMonitorNotification(call);
+    }
+
+    private void stopMonitorWorks(Call call) {
+        stopMonitorNotification(call);
+    }
+
+    private void startMonitorNotification(Call call) {
+        mPendingCalls.add(call);
+        mHandler.postDelayed(() -> {
+            synchronized (mLock) {
+                if (mPendingCalls.contains(call)) {
+                    stopFGSDelegation(call.getTargetPhoneAccount());
+                    mPendingCalls.remove(call);
+                }
+            }
+        }, 5000L);
+    }
+
+    private void stopMonitorNotification(Call call) {
+        mPendingCalls.remove(call);
+    }
+
+    @VisibleForTesting
+    public void setActivityManagerInternal(ActivityManagerInternal ami) {
+        mActivityManagerInternal = ami;
+    }
+
+    @VisibleForTesting
+    public void setNotificationListenerService(NotificationListenerService listener) {
+        mNotificationListener = listener;
+    }
+}
diff --git a/src/com/android/server/telecom/voip/VoipCallTransaction.java b/src/com/android/server/telecom/voip/VoipCallTransaction.java
new file mode 100644
index 0000000..a1cc13c
--- /dev/null
+++ b/src/com/android/server/telecom/voip/VoipCallTransaction.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2022 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.server.telecom.voip;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+
+import com.android.server.telecom.LoggedHandlerExecutor;
+import com.android.server.telecom.TelecomSystem;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Function;
+
+public class VoipCallTransaction {
+    //TODO: add log events
+    protected static final long TIMEOUT_LIMIT = 5000L;
+    protected final AtomicBoolean mCompleted = new AtomicBoolean(false);
+    protected String mTransactionName = this.getClass().getSimpleName();
+    private HandlerThread mHandlerThread;
+    protected Handler mHandler;
+    protected TransactionManager.TransactionCompleteListener mCompleteListener;
+    protected List<VoipCallTransaction> mSubTransactions;
+    private TelecomSystem.SyncRoot mLock;
+
+    public VoipCallTransaction(
+            List<VoipCallTransaction> subTransactions, TelecomSystem.SyncRoot lock) {
+        mSubTransactions = subTransactions;
+        mHandlerThread = new HandlerThread(this.toString());
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+        mLock = lock;
+    }
+
+    public VoipCallTransaction(TelecomSystem.SyncRoot lock) {
+        this(null /** mSubTransactions */, lock);
+    }
+
+    public void start() {
+        // post timeout work
+        mHandler.postDelayed(() -> {
+            if (mCompleted.getAndSet(true)) {
+                return;
+            }
+            if (mCompleteListener != null) {
+                mCompleteListener.onTransactionTimeout(mTransactionName);
+            }
+            finish();
+        }, TIMEOUT_LIMIT);
+
+        scheduleTransaction();
+    }
+
+    protected void scheduleTransaction() {
+        CompletableFuture<Void> future = CompletableFuture.completedFuture(null);
+        future.thenComposeAsync(this::processTransaction,
+                        new LoggedHandlerExecutor(mHandler, mTransactionName + "@"
+                                + hashCode() + ".pT", mLock))
+                .thenApplyAsync(
+                        (Function<VoipCallTransactionResult, Void>) result -> {
+                            mCompleted.set(true);
+                            if (mCompleteListener != null) {
+                                mCompleteListener.onTransactionCompleted(result, mTransactionName);
+                            }
+                            finish();
+                            return null;
+                        });
+    }
+
+    public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+        return CompletableFuture.completedFuture(
+                new VoipCallTransactionResult(VoipCallTransactionResult.RESULT_SUCCEED, null));
+    }
+
+    public void setCompleteListener(TransactionManager.TransactionCompleteListener listener) {
+        mCompleteListener = listener;
+    }
+
+    public void finish() {
+        // finish all sub transactions
+        if (mSubTransactions != null && mSubTransactions.size() > 0) {
+            mSubTransactions.forEach(VoipCallTransaction::finish);
+        }
+        mHandlerThread.quit();
+    }
+}
diff --git a/src/com/android/server/telecom/voip/VoipCallTransactionResult.java b/src/com/android/server/telecom/voip/VoipCallTransactionResult.java
new file mode 100644
index 0000000..2916fc6
--- /dev/null
+++ b/src/com/android/server/telecom/voip/VoipCallTransactionResult.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2022 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.server.telecom.voip;
+
+import com.android.server.telecom.Call;
+
+import java.util.Objects;
+
+public class VoipCallTransactionResult {
+    public static final int RESULT_SUCCEED = 0;
+    public static final int RESULT_FAILED = 1;
+
+    private int mResult;
+    private String mMessage;
+    private Call mCall;
+
+    public VoipCallTransactionResult(int result, String message) {
+        mResult = result;
+        mMessage = message;
+    }
+
+    public VoipCallTransactionResult(int result, Call call, String message) {
+        mResult = result;
+        mCall = call;
+        mMessage = message;
+    }
+
+    public int getResult() {
+        return mResult;
+    }
+
+    public String getMessage() {
+        return mMessage;
+    }
+
+    public Call getCall(){
+        return mCall;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof VoipCallTransactionResult)) return false;
+        VoipCallTransactionResult that = (VoipCallTransactionResult) o;
+        return mResult == that.mResult && Objects.equals(mMessage, that.mMessage);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mResult, mMessage);
+    }
+
+    @Override
+    public String toString() {
+        return new StringBuilder().
+                append("{ VoipCallTransactionResult: [mResult: ").
+                append(mResult).
+                append("], [mCall: ").
+                append(mCall.toString()).
+                append("], [mMessage=").
+                append(mMessage).append("]  }").toString();
+    }
+}
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
index e6e35d8..6670095 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
@@ -87,6 +87,7 @@
         mCallList.notifyConnectionServiceFocusGained();
     }
 
+    @SuppressWarnings("CatchAndPrintStackTrace")
     private Connection createSelfManagedConnection(ConnectionRequest request, boolean isIncoming,
             boolean isHandover) {
         SelfManagedConnection connection = new SelfManagedConnection(mCallList,
@@ -101,11 +102,13 @@
         connection.setCallerDisplayName(TEST_NAMES[random.nextInt(TEST_NAMES.length)],
                 TelecomManager.PRESENTATION_ALLOWED);
         connection.setExtras(request.getExtras());
-        if (isIncoming) {
-            connection.setIsIncomingCallUiShowing(request.shouldShowIncomingCallUi());
-            connection.setRinging();
-        } else {
-            connection.setDialing();
+        if (!request.getAddress().getSchemeSpecificPart().equals("123")) {
+            if (isIncoming) {
+                connection.setIsIncomingCallUiShowing(request.shouldShowIncomingCallUi());
+                connection.setRinging();
+            } else {
+                connection.setDialing();
+            }
         }
         Bundle requestExtras = request.getExtras();
         if (requestExtras != null) {
@@ -139,6 +142,11 @@
         connection.setVideoState(request.getVideoState());
         Log.i(this, "createSelfManagedConnection %s", connection);
         mCallList.addConnection(connection);
+        try {
+            Thread.sleep(8000);
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
         return connection;
     }
 }
diff --git a/testapps/transactionalVoipApp/Android.bp b/testapps/transactionalVoipApp/Android.bp
new file mode 100644
index 0000000..68089e2
--- /dev/null
+++ b/testapps/transactionalVoipApp/Android.bp
@@ -0,0 +1,28 @@
+//
+// Copyright (C) 2022 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "transactionalVoipApp",
+    static_libs: [
+        "androidx.legacy_legacy-support-v4",
+        "guava",
+    ],
+    srcs: ["src/**/*.java"],
+}
diff --git a/testapps/transactionalVoipApp/AndroidManifest.xml b/testapps/transactionalVoipApp/AndroidManifest.xml
new file mode 100644
index 0000000..d0aa50b
--- /dev/null
+++ b/testapps/transactionalVoipApp/AndroidManifest.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+     coreApp="true"
+     package="com.android.server.telecom.transactionalVoipApp">
+
+    <uses-sdk android:minSdkVersion="28"
+         android:targetSdkVersion="33"/>
+
+    <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
+
+    <application android:label="Transactional Voip">
+        <uses-library android:name="android.test.runner"/>
+
+        <activity android:name="com.android.server.telecom.transactionalVoipApp.VoipAppMainActivity"
+                  android:exported="true"
+                  android:label="Transactional Voip">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+    </application>
+</manifest>
diff --git a/testapps/transactionalVoipApp/res/drawable-hdpi/ic_android_black_24dp.png b/testapps/transactionalVoipApp/res/drawable-hdpi/ic_android_black_24dp.png
new file mode 100644
index 0000000..ed3ee45
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/drawable-hdpi/ic_android_black_24dp.png
Binary files differ
diff --git a/testapps/transactionalVoipApp/res/drawable-mdpi/ic_android_black_24dp.png b/testapps/transactionalVoipApp/res/drawable-mdpi/ic_android_black_24dp.png
new file mode 100644
index 0000000..a4add51
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/drawable-mdpi/ic_android_black_24dp.png
Binary files differ
diff --git a/testapps/transactionalVoipApp/res/drawable-xhdpi/ic_android_black_24dp.png b/testapps/transactionalVoipApp/res/drawable-xhdpi/ic_android_black_24dp.png
new file mode 100644
index 0000000..41558f2
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/drawable-xhdpi/ic_android_black_24dp.png
Binary files differ
diff --git a/testapps/transactionalVoipApp/res/drawable-xxhdpi/ic_android_black_24dp.png b/testapps/transactionalVoipApp/res/drawable-xxhdpi/ic_android_black_24dp.png
new file mode 100644
index 0000000..6006b12
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/drawable-xxhdpi/ic_android_black_24dp.png
Binary files differ
diff --git a/testapps/transactionalVoipApp/res/drawable-xxxhdpi/ic_android_black_24dp.png b/testapps/transactionalVoipApp/res/drawable-xxxhdpi/ic_android_black_24dp.png
new file mode 100644
index 0000000..4f935bf
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/drawable-xxxhdpi/ic_android_black_24dp.png
Binary files differ
diff --git a/testapps/transactionalVoipApp/res/layout/main_activity.xml b/testapps/transactionalVoipApp/res/layout/main_activity.xml
new file mode 100644
index 0000000..86d8e20
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/layout/main_activity.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <TextView
+        android:id="@+id/title"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/app_name"/>
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <Button
+            android:id="@+id/registerButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/register_phone_account"/>
+
+        <ToggleButton
+            android:id="@+id/callDirectionButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textOff="@string/direction_outgoing"
+            android:textOn="@string/direction_incoming"
+        />
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+            <Button
+                android:id="@+id/add_call_1_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/add_call_1"/>
+
+            <Button
+                android:id="@+id/disconnect_call_1_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/disconnect_call_1"/>
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+
+            <Button
+                android:id="@+id/add_call_2_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/add_call_2"/>
+
+            <Button
+                android:id="@+id/set_call_2_active_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/set_call_active"/>
+
+            <Button
+                android:id="@+id/disconnect_call_2_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/disconnect_call_2"/>
+        </LinearLayout>
+    </LinearLayout>
+</LinearLayout>
diff --git a/testapps/transactionalVoipApp/res/values-af/strings.xml b/testapps/transactionalVoipApp/res/values-af/strings.xml
new file mode 100644
index 0000000..3e12b43
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-af/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Transactional API-toetsaktiwiteit"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Registreer foonrekening"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"uitgaande"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"inkomend"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"voeg oproep 1 by"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"beëindig oproep 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"voeg oproep 2 by"</string>
+    <string name="set_call_active" msgid="248748409907478011">"stel oproep 2 as aktief"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"beëindig oproep 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-am/strings.xml b/testapps/transactionalVoipApp/res/values-am/strings.xml
new file mode 100644
index 0000000..9aba40a
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-am/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"የግብይት ኤፒአይ ሙከራ እንቅስቃሴ"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"የስልክ መለያ መዝግብ"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"ወጪ"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"መጪ"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"ጥሪ 1ን አክል"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"የጥሪ 1ን ግንኙነት አቋርጥ"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"ጥሪ 2ን አክል"</string>
+    <string name="set_call_active" msgid="248748409907478011">"ጥሪ 2ን ወደ ንቁ አቀናብር"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"የጥሪ 2ን ግንኙነት አቋርጥ"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-ar/strings.xml b/testapps/transactionalVoipApp/res/values-ar/strings.xml
new file mode 100644
index 0000000..c76746b
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-ar/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"نشاط اختبار واجهة برمجة التطبيقات من خلال المعاملات"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"تسجيل حساب الهاتف"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"الصادرة"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"الواردة"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"إضافة المكالمة 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"قطع المكالمة 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"إضافة المكالمة 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"ضبط حالة المكالمة 2 على نشطة"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"قطع المكالمة 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-as/strings.xml b/testapps/transactionalVoipApp/res/values-as/strings.xml
new file mode 100644
index 0000000..66fae5f
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-as/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"লেনদেন সম্বন্ধীয় API পৰীক্ষণৰ কাৰ্যকলাপ"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"ফ\'নৰ একাউণ্ট পঞ্জীয়ন কৰক"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"বহিৰ্গামী"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"অন্তৰ্গামী"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"কল ১ যোগ কৰক"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"কল ১ৰ সংযোগ বিচ্ছিন্ন কৰক"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"কল ২ যোগ কৰক"</string>
+    <string name="set_call_active" msgid="248748409907478011">"কল ২ক সক্ৰিয় হিচাপে ছেট কৰক"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"কল ২ৰ সংযোগ বিচ্ছিন্ন কৰক"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-az/strings.xml b/testapps/transactionalVoipApp/res/values-az/strings.xml
new file mode 100644
index 0000000..1bdb545
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-az/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Tranzaksiya ilə bağlı API test Fəaliyyəti"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Telefon Hesabını Qeydiyyatdan Keçirin"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"gedən"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"gələn"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"1-ci zəngi əlavə edin"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"1-ci zəngi bitirin"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"2-ci zəngi əlavə edin"</string>
+    <string name="set_call_active" msgid="248748409907478011">"2-ci zəngi aktivləşdirin"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"2-ci zəngi bitirin"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-b+sr+Latn/strings.xml b/testapps/transactionalVoipApp/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..ad4605d
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Aktivnost testiranja transakcionog API-ja"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Registruj nalog telefona"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"odlazni"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"dolazni"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"dodaj 1. poziv"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"prekini 1. poziv"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"dodaj 2. poziv"</string>
+    <string name="set_call_active" msgid="248748409907478011">"podesi 2. poziv kao aktivan"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"prekini 2. poziv"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-be/strings.xml b/testapps/transactionalVoipApp/res/values-be/strings.xml
new file mode 100644
index 0000000..ecb1464
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-be/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Праверачныя дзеянні API трансакцый"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Зарэгістраваць уліковы запіс тэлефона"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"выходны"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"уваходны"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"дадаць выклік 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"завяршыць выклік 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"дадаць выклік 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"зрабіць выклік 2 актыўным"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"завяршыць выклік 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-bg/strings.xml b/testapps/transactionalVoipApp/res/values-bg/strings.xml
new file mode 100644
index 0000000..c822ad5
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-bg/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Активност за тестване на API за транзакции"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Регистриране на профила на телефона"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"изходящо"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"входящо"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"добавяне на обаждане 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"прекратяване на обаждане 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"добавяне на обаждане 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"задаване на обаждане 2 като активно"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"прекратяване на обаждане 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-bn/strings.xml b/testapps/transactionalVoipApp/res/values-bn/strings.xml
new file mode 100644
index 0000000..af4ecbc
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-bn/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Transactional API টেস্ট সংক্রান্ত অ্যাক্টিভিটি"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"ফোনের অ্যাকাউন্ট রেজিস্টার করুন"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"আউটগোয়িং"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"ইনকামিং"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"কল ১ যোগ করুন"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"কল ১ ডিসকানেক্ট করুন"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"কল ২ যোগ করুন"</string>
+    <string name="set_call_active" msgid="248748409907478011">"কল ২ চালু আছে হিসেবে সেট করুন"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"কল ২ ডিসকানেক্ট করুন"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-bs/strings.xml b/testapps/transactionalVoipApp/res/values-bs/strings.xml
new file mode 100644
index 0000000..96cedc5
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-bs/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Aktivnost testa transakcijskog API-ja"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Registrirajte račun telefona"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"odlazno"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"dolazno"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"dodaj poziv 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"prekini poziv 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"dodaj poziv 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"postavi poziv 2 kao aktivan"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"prekini poziv 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-ca/strings.xml b/testapps/transactionalVoipApp/res/values-ca/strings.xml
new file mode 100644
index 0000000..ab16064
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-ca/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Activitat de prova de l\'API transaccional"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Registra el compte del telèfon"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"sortint"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"entrant"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"afegeix la trucada 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"desconnecta la trucada 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"afegeix la trucada 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"defineix la trucada 2 com a activa"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"desconnecta la trucada 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-cs/strings.xml b/testapps/transactionalVoipApp/res/values-cs/strings.xml
new file mode 100644
index 0000000..b7be57f
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-cs/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Aktivita testování v transakčním rozhraní API"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Registrovat telefonní účet"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"odchozí"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"příchozí"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"přidat hovor 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"odpojit hovor 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"přidat hovor 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"nastavit hovor 2 jako aktivní"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"odpojit hovor 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-da/strings.xml b/testapps/transactionalVoipApp/res/values-da/strings.xml
new file mode 100644
index 0000000..46e6a33
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-da/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Testaktivitet for transaktions-API"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Registrer telefonkonto"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"udgående"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"indgående"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"tilføj opkald 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"afslut opkald 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"tilføj opkald 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"konfigurer opkald 2 som aktivt"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"afslut opkald 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-de/strings.xml b/testapps/transactionalVoipApp/res/values-de/strings.xml
new file mode 100644
index 0000000..b7d04e0
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-de/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Testaktivität zur transaktionalen API"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Telefonkonto registrieren"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"Ausgehend"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"Eingehend"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"Anruf 1 hinzufügen"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"Anruf 1 trennen"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"Anruf 2 hinzufügen"</string>
+    <string name="set_call_active" msgid="248748409907478011">"Anruf 2 aktivieren"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"Anruf 2 trennen"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-el/strings.xml b/testapps/transactionalVoipApp/res/values-el/strings.xml
new file mode 100644
index 0000000..bb5b0d9
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-el/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Δοκιμαστική δραστηριότητα API συναλλαγών"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Εγγραφή λογαριασμού τηλεφώνου"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"εξερχόμενη"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"εισερχόμενη"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"προσθήκη κλήσης 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"αποσύνδεση κλήσης 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"προσθήκη κλήσης 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"ορισμός κλήσης 2 ως ενεργής"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"αποσύνδεση κλήσης 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-en-rAU/strings.xml b/testapps/transactionalVoipApp/res/values-en-rAU/strings.xml
new file mode 100644
index 0000000..95c71e4
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-en-rAU/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Transactional API test activity"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Register phone account"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"outgoing"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"incoming"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"add call 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"disconnect call 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"add call 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"set call 2 active"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"disconnect call 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-en-rCA/strings.xml b/testapps/transactionalVoipApp/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000..0c705e3
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-en-rCA/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Transactional API test Activity"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Register Phone Account"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"outgoing"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"incoming"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"add call 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"disconnect call 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"add call 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"set call 2 active"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"disconnect call 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-en-rGB/strings.xml b/testapps/transactionalVoipApp/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..95c71e4
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-en-rGB/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Transactional API test activity"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Register phone account"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"outgoing"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"incoming"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"add call 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"disconnect call 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"add call 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"set call 2 active"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"disconnect call 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-en-rIN/strings.xml b/testapps/transactionalVoipApp/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..95c71e4
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-en-rIN/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Transactional API test activity"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Register phone account"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"outgoing"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"incoming"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"add call 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"disconnect call 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"add call 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"set call 2 active"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"disconnect call 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-en-rXC/strings.xml b/testapps/transactionalVoipApp/res/values-en-rXC/strings.xml
new file mode 100644
index 0000000..2e69f39
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-en-rXC/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‎‏‎‏‎‎‎‎‏‎‏‏‎‏‎‏‎‎‏‏‎‎‎‏‎‏‏‏‎‎‎‎‎‏‏‏‎‎‎‏‎‎‎‏‏‎‏‏‏‏‎‏‎‎‎‏‎‎‎‎‎‏‏‎Transactional API test Activity‎‏‎‎‏‎"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‎‏‏‏‎‏‎‏‎‏‎‎‏‏‎‎‏‎‏‎‏‎‏‎‎‏‏‎‎‎‏‏‎‏‏‎‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‏‏‏‎‏‏‏‏‏‏‎‎‎Register Phone Account‎‏‎‎‏‎"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‎‏‎‎‎‏‏‎‎‏‎‏‏‏‏‎‏‏‏‎‎‎‎‏‏‎‎‎‎‎‎‎‎‏‏‎‏‎‏‏‎‎‏‏‏‎‏‎‏‏‏‎‏‏‎‎‏‏‎‏‎‎‎‎outgoing‎‏‎‎‏‎"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‎‏‎‏‏‎‏‏‎‎‎‎‏‎‎‏‎‎‏‏‏‎‏‏‏‎‎‎‏‎‏‏‎‎‏‎‏‎‎‏‎‎‎‎‏‎‎‏‏‎‎‎‎‎‏‏‏‏‏‎‎‏‏‎incoming‎‏‎‎‏‎"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‎‏‎‎‎‎‏‏‎‏‏‎‎‏‎‎‎‏‎‎‎‏‎‎‏‏‏‎‏‏‎‏‏‏‎‏‎‏‎‎‎‏‏‏‎‎‏‏‎‎‏‎‎‎‎‏‎‏‏‎‎‏‎add call 1‎‏‎‎‏‎"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‎‏‎‎‏‎‏‎‏‎‎‏‏‎‏‏‎‎‎‏‏‏‎‎‎‎‏‏‎‏‏‏‏‎‎‎‏‏‏‎‎‎‏‎‎‎‏‏‎‎‎‏‏‎‎‏‎‎‎‏‎‏‏‎disconnect call 1‎‏‎‎‏‎"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‏‏‏‎‏‏‏‎‏‎‎‎‏‎‎‎‎‏‎‎‎‎‏‎‎‎‏‎‎‎‎‎‎‏‎‎‏‎‎‏‏‎‏‏‏‏‎‏‎‎‏‏‏‎‎‏‎‏‎‏‏‏‎‏‎‎add call 2‎‏‎‎‏‎"</string>
+    <string name="set_call_active" msgid="248748409907478011">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‎‏‎‏‏‎‏‏‏‎‎‏‏‏‎‏‏‏‎‏‏‎‏‎‏‏‏‎‎‎‏‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‏‏‎set call 2 active‎‏‎‎‏‎"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‎‎‏‏‏‏‎‏‏‎‎‎‏‏‏‎‏‎‎‏‏‏‏‏‎‎‎‏‎‏‏‎‏‎‏‏‎‎‏‏‎‎‎‏‎‏‏‎‎‏‏‎‏‏‏‏‏‏‎‎‎disconnect call 2‎‏‎‎‏‎"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-es-rUS/strings.xml b/testapps/transactionalVoipApp/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..06098c4
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-es-rUS/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Actividad de prueba de la API transaccional"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Registrar cuenta telefónica"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"saliente"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"entrante"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"agregar llamada 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"desconectar la llamada 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"agregar llamada 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"establecer llamada 2 activa"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"desconectar la llamada 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-es/strings.xml b/testapps/transactionalVoipApp/res/values-es/strings.xml
new file mode 100644
index 0000000..1391d3c
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-es/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Actividad de prueba de API transaccional"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Registrar cuenta de teléfono"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"saliente"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"entrante"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"añadir llamada 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"desconectar llamada 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"añadir llamada 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"activar llamada 2"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"desconectar llamada 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-et/strings.xml b/testapps/transactionalVoipApp/res/values-et/strings.xml
new file mode 100644
index 0000000..c2dbe2e
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-et/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Tehingupõhise API testimise tegevus"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Telefonikonto registreerimine"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"väljaminevad"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"sissetulevad"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"lisa kõne 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"lõpeta kõne 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"lisa kõne 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"kõne 2 aktiivseks seadmine"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"lõpeta kõne 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-eu/strings.xml b/testapps/transactionalVoipApp/res/values-eu/strings.xml
new file mode 100644
index 0000000..f3b0406
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-eu/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Transakzio bidezko APIen proba-jarduerak"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Erregistratu telefonoaren kontua"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"irteerakoa"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"sarrerakoa"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"gehitu 1. deia"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"deskonektatu 1. deia"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"gehitu 2. deia"</string>
+    <string name="set_call_active" msgid="248748409907478011">"ezarri 2. deia aktibo gisa"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"deskonektatu 2. deia"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-fa/strings.xml b/testapps/transactionalVoipApp/res/values-fa/strings.xml
new file mode 100644
index 0000000..634f55e
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-fa/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"‏فعالیت آزمایشی Transactional API"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"ثبت حساب تلفن"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"خروجی"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"ورودی"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"افزودن تماس ۱"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"قطع تماس ۱"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"افزودن تماس ۲"</string>
+    <string name="set_call_active" msgid="248748409907478011">"تنظیم تماس ۲ روی حالت فعال"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"قطع تماس ۲"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-fi/strings.xml b/testapps/transactionalVoipApp/res/values-fi/strings.xml
new file mode 100644
index 0000000..6c9c5f7
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-fi/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Tapahtuman API-testitoiminta"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Rekisteröi puhelintili"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"lähtevä"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"saapuva"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"lisää puhelu 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"katkaise puhelu 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"lisää puhelu 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"aseta puhelu 2 aktiiviseksi"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"katkaise puhelu 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-fr-rCA/strings.xml b/testapps/transactionalVoipApp/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..5c9a397
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-fr-rCA/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Activité de test de l\'API transactionnelle"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Inscrire un compte téléphonique"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"sortant"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"entrant"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"ajouter l\'appel 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"déconnecter l\'appel 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"ajouter l\'appel 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"définir l\'appel 2 comme actif"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"déconnecter l\'appel 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-fr/strings.xml b/testapps/transactionalVoipApp/res/values-fr/strings.xml
new file mode 100644
index 0000000..48d8062
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-fr/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Activité de test de l\'API transactionnelle"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Enregistrer un compte de téléphonie"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"sortant"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"entrant"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"ajouter un appel 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"mettre fin à l\'appel 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"ajouter un appel 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"définir l\'appel 2 comme actif"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"mettre fin à l\'appel 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-gl/strings.xml b/testapps/transactionalVoipApp/res/values-gl/strings.xml
new file mode 100644
index 0000000..70cc9f6
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-gl/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Actividade de proba da API transaccional"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Rexistrar conta do teléfono"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"saínte"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"entrante"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"engadir chamada 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"desconectar chamada 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"engadir chamada 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"definir chamada 2 como activa"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"desconectar chamada 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-gu/strings.xml b/testapps/transactionalVoipApp/res/values-gu/strings.xml
new file mode 100644
index 0000000..d9935ae
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-gu/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Transactional APIના પરીક્ષણની પ્રવૃત્તિ"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"ફોન એકાઉન્ટ રજિસ્ટર કરો"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"આઉટગોઇંગ"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"ઇનકમિંગ"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"કૉલ 1 ઉમેરો"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"કૉલ 1 ડિસ્કનેક્ટ કરો"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"કૉલ 2 ઉમેરો"</string>
+    <string name="set_call_active" msgid="248748409907478011">"કૉલ 2ને સક્રિય પર સેટ કરો"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"કૉલ 2 ડિસ્કનેક્ટ કરો"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-hi/strings.xml b/testapps/transactionalVoipApp/res/values-hi/strings.xml
new file mode 100644
index 0000000..9121a37
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-hi/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Transactional API से जुड़ी टेस्ट गतिविधि"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Phone Account में रजिस्टर करें"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"किए जाने वाले (आउटगोइंग) कॉल"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"आने वाले (इनकमिंग) कॉल"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"कॉल 1 जोड़ें"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"कॉल 1 को डिसकनेक्ट करें"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"कॉल 2 जोड़ें"</string>
+    <string name="set_call_active" msgid="248748409907478011">"कॉल 2 को \'चालू है\' के तौर पर सेट करें"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"कॉल 2 को डिसकनेक्ट करें"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-hr/strings.xml b/testapps/transactionalVoipApp/res/values-hr/strings.xml
new file mode 100644
index 0000000..68291d6
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-hr/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Testna aktivnost API-ja za transakcije"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Registracija telefonskog računa"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"odlazni"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"dolazni"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"dodavanje 1. poziva"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"prekid 1. poziva"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"dodavanje 2. poziva"</string>
+    <string name="set_call_active" msgid="248748409907478011">"postavljane 2. poziva kao aktivnog"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"prekid 2. poziva"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-hu/strings.xml b/testapps/transactionalVoipApp/res/values-hu/strings.xml
new file mode 100644
index 0000000..fc903f8
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-hu/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Tranzakciós API-teszttevékenység"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Telefonáláshoz használt fiók regisztrálása"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"kimenő"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"bejövő"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"1. hívás hozzáadása"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"1. hívás megszakítása"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"2. hívás hozzáadása"</string>
+    <string name="set_call_active" msgid="248748409907478011">"2. hívás aktívra állítása"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"2. hívás megszakítása"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-hy/strings.xml b/testapps/transactionalVoipApp/res/values-hy/strings.xml
new file mode 100644
index 0000000..a781b20
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-hy/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Գործարքային API-ների փորձարկման գործողություն"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Հեռախոսի հաշվի գրանցում"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"ելքային"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"մուտքային"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"ավելացնել զանգ 1-ը"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"ընդհատել զանգ 1-ը"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"ավելացնել զանգ 2-ը"</string>
+    <string name="set_call_active" msgid="248748409907478011">"զանգ 2-ը դարձնել ակտիվ"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"ընդհատել զանգ 2-ը"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-in/strings.xml b/testapps/transactionalVoipApp/res/values-in/strings.xml
new file mode 100644
index 0000000..0b1f12f
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-in/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Aktivitas pengujian API Transaksional"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Daftarkan Akun Ponsel"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"keluar"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"masuk"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"tambahkan panggilan 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"akhiri panggilan 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"tambahkan panggilan 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"setel panggilan 2 ke aktif"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"akhiri panggilan 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-is/strings.xml b/testapps/transactionalVoipApp/res/values-is/strings.xml
new file mode 100644
index 0000000..c067c74
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-is/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Prófun á virkni forritaskila færslna"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Skrá símareikning"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"hringt"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"móttekið"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"bæta við símtali 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"slíta símtali 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"bæta við símtali 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"virkja símtal 2"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"slíta símtali 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-it/strings.xml b/testapps/transactionalVoipApp/res/values-it/strings.xml
new file mode 100644
index 0000000..35da8ae
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-it/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Attività di test dell\'API transazionale"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Registra account telefono"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"in uscita"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"in arrivo"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"aggiungi chiamata 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"termina chiamata 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"aggiungi chiamata 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"imposta chiamata 2 attiva"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"termina chiamata 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-iw/strings.xml b/testapps/transactionalVoipApp/res/values-iw/strings.xml
new file mode 100644
index 0000000..95557df
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-iw/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Transactional API test Activity"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"רישום חשבון הטלפון"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"שיחה יוצאת"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"שיחה נכנסת"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"הוספת שיחה 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"ניתוק שיחה 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"הוספת שיחה 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"הגדרת שיחה 2 פעילה"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"ניתוק שיחה 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-ja/strings.xml b/testapps/transactionalVoipApp/res/values-ja/strings.xml
new file mode 100644
index 0000000..86d6189
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-ja/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Transactional API テスト アクティビティ"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"スマートフォン アカウントを登録"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"発信"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"着信"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"通話 1 を追加"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"通話 1 を切る"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"通話 2 を追加"</string>
+    <string name="set_call_active" msgid="248748409907478011">"通話 2 をアクティブに設定"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"通話 2 を切る"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-ka/strings.xml b/testapps/transactionalVoipApp/res/values-ka/strings.xml
new file mode 100644
index 0000000..1f3e69e
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-ka/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"ტრანზაქციული API ტესტის აქტივობა"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"ტელეფონის ანგარიშის რეგისტრაცია"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"გამავალი"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"შემომავალი"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"ზარი 1-ის დამატება"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"ზარი 1-ის გათიშვა"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"ზარი 2-ის დამატება"</string>
+    <string name="set_call_active" msgid="248748409907478011">"ზარი 2-ის აქტივაცია"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"ზარის 2-ის გათიშვა"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-kk/strings.xml b/testapps/transactionalVoipApp/res/values-kk/strings.xml
new file mode 100644
index 0000000..a82c02a
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-kk/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Транзакциялық API сынағына қатысты әрекет"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Телефон аккаунтын тіркеу"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"шығыс"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"кіріс"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"1-қоңырауды қосу"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"1-қоңырауды ажырату"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"2-қоңырауды қосу"</string>
+    <string name="set_call_active" msgid="248748409907478011">"2-қоңырауды белсенді қылу"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"2-қоңырауды ажырату"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-km/strings.xml b/testapps/transactionalVoipApp/res/values-km/strings.xml
new file mode 100644
index 0000000..518fda6
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-km/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"សកម្មភាព​ធ្វើតេស្ត API ប្រតិបត្តិការ"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"ចុះឈ្មោះ​គណនី​ទូរសព្ទ"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"ចេញ"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"ចូល"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"បញ្ចូល​ការហៅ​ទូរសព្ទទី 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"ផ្ដាច់​ការហៅទូរសព្ទទី 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"បញ្ចូល​ការហៅ​ទូរសព្ទទី 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"កំណត់​ការហៅ​ទូរសព្ទទី 2 ឱ្យសកម្ម"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"ផ្ដាច់​ការហៅទូរសព្ទទី 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-kn/strings.xml b/testapps/transactionalVoipApp/res/values-kn/strings.xml
new file mode 100644
index 0000000..094bf01
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-kn/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"ಟ್ರಾನ್ಸಾಕ್ಷನಲ್ API ಪರೀಕ್ಷಾ ಚಟುವಟಿಕೆ"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"ಫೋನ್ ಖಾತೆಯನ್ನು ನೋಂದಾಯಿಸಿ"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"ಹೊರಹೋಗುವುದು"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"ಒಳಬರುವುದು"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"ಕರೆ 1 ಅನ್ನು ಸೇರಿಸಿ"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"ಕರೆ 1 ಅನ್ನು ಕಡಿತಗೊಳಿಸಿ"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"ಕರೆ 2 ಅನ್ನು ಸೇರಿಸಿ"</string>
+    <string name="set_call_active" msgid="248748409907478011">"ಕರೆ 2 ಅನ್ನು ಸಕ್ರಿಯ ಎಂದು ಸೆಟ್ ಮಾಡಿ"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"ಕರೆ 2 ಅನ್ನು ಕಡಿತಗೊಳಿಸಿ"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-ko/strings.xml b/testapps/transactionalVoipApp/res/values-ko/strings.xml
new file mode 100644
index 0000000..10c9f62
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-ko/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"트랜잭션 API 테스트 활동"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"전화 계정 등록"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"발신"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"수신"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"통화 1 추가"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"통화 1 끊기"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"통화 2 추가"</string>
+    <string name="set_call_active" msgid="248748409907478011">"통화 2 활성으로 설정"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"통화 2 끊기"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-ky/strings.xml b/testapps/transactionalVoipApp/res/values-ky/strings.xml
new file mode 100644
index 0000000..06bff74
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-ky/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Транзакциялык API сыноосунун активдүүлүгү"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Телефон аккаунтун каттоо"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"чыгуучу"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"келүүчү"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"1-чалууну кошуу"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"1-чалууну үзүү"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"2-чалууну кошуу"</string>
+    <string name="set_call_active" msgid="248748409907478011">"2-чалууну активдүү кылуу"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"2-чалууну үзүү"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-lo/strings.xml b/testapps/transactionalVoipApp/res/values-lo/strings.xml
new file mode 100644
index 0000000..f5f6dd5
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-lo/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"ກິດຈະກໍາການທົດສອບ API ທຸລະກໍາ"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"ລົງທະບຽນບັນຊີໂທລະສັບ"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"ສາຍໂທອອກ"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"ສາຍໂທເຂົ້າ"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"ເພີ່ມການໂທ 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"ຕັດເຊື່ອມຕໍ່ການໂທ 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"ເພີ່ມການໂທ 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"ຕັ້ງການໂທ 2 ເປັນນຳໃຊ້"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"ຕັດເຊື່ອມຕໍ່ການໂທ 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-lt/strings.xml b/testapps/transactionalVoipApp/res/values-lt/strings.xml
new file mode 100644
index 0000000..5e66c15
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-lt/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Operacijų API testavimo veikla"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Užregistruoti telefono paskyrą"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"siunčiamieji"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"gaunamieji"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"pridėti 1 skambutį"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"atjungti 1 skambutį"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"pridėti 2 skambutį"</string>
+    <string name="set_call_active" msgid="248748409907478011">"nustatyti 2 skambutį kaip aktyvų"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"atjungti 2 skambutį"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-lv/strings.xml b/testapps/transactionalVoipApp/res/values-lv/strings.xml
new file mode 100644
index 0000000..6678d6e
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-lv/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Transakciju API testa darbība"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Reģistrēt tālruņa kontu"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"izejošs"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"ienākošs"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"pievienot 1. zvanu"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"pārtraukt 1. zvanu"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"pievienot 2. zvanu"</string>
+    <string name="set_call_active" msgid="248748409907478011">"iestatīt 2. zvanu kā aktīvu"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"pārtraukt 2. zvanu"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-mk/strings.xml b/testapps/transactionalVoipApp/res/values-mk/strings.xml
new file mode 100644
index 0000000..01cd42e
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-mk/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Активност на тестирање на API за трансакции"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Регистрирај телефонска сметка"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"појдовен"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"дојдовен"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"додај повик 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"прекини го повикот 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"додај повик 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"постави го повик 2 како активен"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"прекини го повикот 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-ml/strings.xml b/testapps/transactionalVoipApp/res/values-ml/strings.xml
new file mode 100644
index 0000000..66ec4ef
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-ml/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"ട്രാൻസാക്ഷണൽ API ടെസ്റ്റ് ആക്റ്റിവിറ്റി"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"ഫോൺ അക്കൗണ്ട് രജിസ്റ്റർ ചെയ്യുക"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"ഔട്ട്‌ഗോയിംഗ്"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"ഇൻകമിംഗ്"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"കോൾ 1 ചേർക്കുക"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"കോൾ 1 വിച്ഛേദിക്കുക"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"കോൾ 2 ചേർക്കുക"</string>
+    <string name="set_call_active" msgid="248748409907478011">"കോൾ 2 സജീവമായി സജ്ജീകരിക്കുക"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"കോൾ 2 വിച്ഛേദിക്കുക"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-mn/strings.xml b/testapps/transactionalVoipApp/res/values-mn/strings.xml
new file mode 100644
index 0000000..f056b2b
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-mn/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Гүйлгээний API-н туршилтын үйл ажиллагаа"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Утасны бүртгэл бүртгүүлэх"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"залгаж буй"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"ирж буй"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"1-р дуудлага нэмэх"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"1-р дуудлагыг салгах"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"2-р дуудлага нэмэх"</string>
+    <string name="set_call_active" msgid="248748409907478011">"2-р дуудлагыг идэвхтэй болгож тохируулах"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"2-р дуудлагыг салгах"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-mr/strings.xml b/testapps/transactionalVoipApp/res/values-mr/strings.xml
new file mode 100644
index 0000000..4ca3b8e
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-mr/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"व्यावहारिक API चाचणी अ‍ॅक्टिव्हिटी"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"फोन खात्याची नोंदणी करा"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"आउटगोइंग"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"इनकमिंग"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"कॉल १ जोडा"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"कॉल १ डिस्कनेक्ट करा"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"कॉल २ जोडा"</string>
+    <string name="set_call_active" msgid="248748409907478011">"कॉल २ अ‍ॅक्टिव्ह यावर सेट करा"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"कॉल २ डिस्कनेक्ट करा"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-ms/strings.xml b/testapps/transactionalVoipApp/res/values-ms/strings.xml
new file mode 100644
index 0000000..d888672
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-ms/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Aktiviti ujian API transaksi"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Daftar Akaun Telefon"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"keluar"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"masuk"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"tambahkan panggilan 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"putuskan panggilan 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"tambahkan panggilan 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"tetapkan panggilan 2 sebagai aktif"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"putuskan panggilan 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-my/strings.xml b/testapps/transactionalVoipApp/res/values-my/strings.xml
new file mode 100644
index 0000000..7422922
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-my/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"အသိအမှတ်ပြုမှုဆိုင်ရာ API စမ်းသပ်လုပ်ဆောင်ချက်"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"ဖုန်းအကောင့် မှတ်ပုံတင်ရန်"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"အထွက်"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"အဝင်"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"ခေါ်ဆိုမှု ၁ ထည့်ရန်"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"ခေါ်ဆိုမှု ၁ ဖြတ်တောက်ရန်"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"ခေါ်ဆိုမှု ၂ ထည့်ရန်"</string>
+    <string name="set_call_active" msgid="248748409907478011">"ခေါ်ဆိုမှု ၂ ကို လက်ရှိပြောနေကြောင်း သတ်မှတ်ရန်"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"ခေါ်ဆိုမှု ၂ ဖြတ်တောက်ရန်"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-nb/strings.xml b/testapps/transactionalVoipApp/res/values-nb/strings.xml
new file mode 100644
index 0000000..124e0a0
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-nb/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Testaktivitet for Transactional API"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Registrer telefonkonto"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"utgående"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"innkommende"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"legg til anrop 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"avslutt anrop 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"legg til anrop 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"angi anrop 2 som aktivt"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"avslutt anrop 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-ne/strings.xml b/testapps/transactionalVoipApp/res/values-ne/strings.xml
new file mode 100644
index 0000000..84481ec
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-ne/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Transactional API को परीक्षणसम्बन्धी गतिविधि"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"फोन खाता दर्ता गर्नुहोस्"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"बहिर्गमन"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"आगमन"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"कल १ कनेक्ट गर्नुहोस्"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"कल १ डिस्कनेक्ट गर्नुहोस्"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"कल २ कनेक्ट गर्नुहोस्"</string>
+    <string name="set_call_active" msgid="248748409907478011">"कल २ लाई सक्रिय कलका रूपमा सेट गर्नुहोस्"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"कल २ डिस्कनेक्ट गर्नुहोस्"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-nl/strings.xml b/testapps/transactionalVoipApp/res/values-nl/strings.xml
new file mode 100644
index 0000000..949d71e
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-nl/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Testactiviteit Transactional API"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Telefoonaccount registreren"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"uitgaand"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"binnenkomend"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"gesprek 1 toevoegen"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"gesprek 1 beëindigen"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"gesprek 2 toevoegen"</string>
+    <string name="set_call_active" msgid="248748409907478011">"gesprek 2 instellen als actief"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"gesprek 2 beëindigen"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-or/strings.xml b/testapps/transactionalVoipApp/res/values-or/strings.xml
new file mode 100644
index 0000000..7cda1c8
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-or/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"ଟ୍ରାଞ୍ଜେକସନାଲ API ପରୀକ୍ଷଣର କାର୍ଯ୍ୟକଳାପ"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"ଫୋନ ଆକାଉଣ୍ଟର ପଞ୍ଜିକରଣ କରନ୍ତୁ"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"ଆଉଟଗୋଇଂ"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"ଇନକମିଂ"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"କଲ 1 ଯୋଗ କରନ୍ତୁ"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"କଲ 1 ଡିସକନେକ୍ଟ କରନ୍ତୁ"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"କଲ 2 ଯୋଗ କରନ୍ତୁ"</string>
+    <string name="set_call_active" msgid="248748409907478011">"କଲ 2କୁ ସକ୍ରିୟ ଭାବେ ସେଟ କରନ୍ତୁ"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"କଲ 2 ଡିସକନେକ୍ଟ କରନ୍ତୁ"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-pa/strings.xml b/testapps/transactionalVoipApp/res/values-pa/strings.xml
new file mode 100644
index 0000000..c01d6a3
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-pa/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"ਲੈਣ-ਦੇਣ API ਜਾਂਚ ਸਰਗਰਮੀ"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"ਫ਼ੋਨ ਖਾਤਾ ਰਜਿਸਟਰ ਕਰੋ"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"ਆਊਟਗੋਇੰਗ"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"ਇਨਕਮਿੰਗ"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"ਕਾਲ 1 ਸ਼ਾਮਲ ਕਰੋ"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"ਕਾਲ 1 ਕੱਟੋ"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"ਕਾਲ 2 ਸ਼ਾਮਲ ਕਰੋ"</string>
+    <string name="set_call_active" msgid="248748409907478011">"ਕਾਲ 2 ਨੂੰ ਕਿਰਿਆਸ਼ੀਲ ਵਜੋਂ ਸੈੱਟ ਕਰੋ"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"ਕਾਲ 2 ਕੱਟੋ"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-pl/strings.xml b/testapps/transactionalVoipApp/res/values-pl/strings.xml
new file mode 100644
index 0000000..60f370b
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-pl/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Czynność testowa dotycząca transakcji związanej z interfejsem API"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Zarejestruj konto telefonu"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"wychodzące"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"przychodzące"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"dodaj połączenie 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"rozłącz połączenie 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"dodaj połączenie 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"ustaw połączenie 2 jako aktywne"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"rozłącz połączenie 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-pt-rPT/strings.xml b/testapps/transactionalVoipApp/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..ebeb482
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-pt-rPT/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Atividade de teste da API transacional"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Registar conta do telemóvel"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"feita"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"recebida"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"adicionar chamada 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"desligar chamada 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"adicionar chamada 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"definir chamada 2 como ativa"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"desligar chamada 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-pt/strings.xml b/testapps/transactionalVoipApp/res/values-pt/strings.xml
new file mode 100644
index 0000000..ea46628
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-pt/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Atividade de teste da API transacional"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Registrar conta telefônica"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"realizada"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"recebida"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"adicionar ligação 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"encerrar ligação 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"adicionar ligação 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"definir a ligação 2 como ativa"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"encerrar ligação 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-ro/strings.xml b/testapps/transactionalVoipApp/res/values-ro/strings.xml
new file mode 100644
index 0000000..ee6bfa2
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-ro/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Activitate de testare a API-ului tranzacțional"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Înregistrează contul de telefon"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"efectuat"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"primit"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"adaugă apelul 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"deconectează apelul 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"adaugă apelul 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"setează apelul 2 ca activ"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"deconectează apelul 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-ru/strings.xml b/testapps/transactionalVoipApp/res/values-ru/strings.xml
new file mode 100644
index 0000000..5345e7b
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-ru/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Активность тестирования API транзакций"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Зарегистрировать аккаунт телефона"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"исходящий"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"входящий"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"добавить звонок 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"отключить звонок 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"добавить звонок 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"сделать звонок 2 активным"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"отключить звонок 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-si/strings.xml b/testapps/transactionalVoipApp/res/values-si/strings.xml
new file mode 100644
index 0000000..87d421c
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-si/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"ගනුදෙනු API පරීක්ෂණ ක්‍රියාකාරකම්"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"දුරකථන ගිණුම ලියාපදිංචි කරන්න"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"පිටතට යන"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"ඇතුළට එන"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"ඇමතුම 1 එක් කරන්න"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"ඇමතුම 1 විසන්ධි කරන්න"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"ඇමතුම 2 එක් කරන්න"</string>
+    <string name="set_call_active" msgid="248748409907478011">"ඇමතුම 2 සක්‍රිය ලෙස සකසන්න"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"ඇමතුම 2 විසන්ධි කරන්න"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-sk/strings.xml b/testapps/transactionalVoipApp/res/values-sk/strings.xml
new file mode 100644
index 0000000..1d08d78
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-sk/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Testovacia aktivita transakčného rozhrania API"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Registrovať telefónny účet"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"odchádzajúci"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"prichádzajúci"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"pridať hovor 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"odpojiť hovor 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"pridať hovor 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"nastaviť hovor 2 ako aktívny"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"odpojiť hovor 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-sl/strings.xml b/testapps/transactionalVoipApp/res/values-sl/strings.xml
new file mode 100644
index 0000000..c82c86d
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-sl/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Preizkusna dejavnost transakcijskega API-ja"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Registracija telefonskega računa"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"odhodni"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"dohodni"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"dodaj klic 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"prekinitev klica 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"dodaj klic 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"nastavi klic 2 kot aktiven"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"prekinitev klica 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-sq/strings.xml b/testapps/transactionalVoipApp/res/values-sq/strings.xml
new file mode 100644
index 0000000..8a44cdd
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-sq/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Aktiviteti i testimit të API-së së transaksioneve"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Regjistro llogarinë e telefonit"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"dalëse"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"hyrëse"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"shto telefonatën 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"shkëput telefonatën 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"shto telefonatën 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"caktoje telefonatën 2 si aktive"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"shkëput telefonatën 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-sr/strings.xml b/testapps/transactionalVoipApp/res/values-sr/strings.xml
new file mode 100644
index 0000000..8e66da7
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-sr/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Активност тестирања трансакционог API-ја"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Региструј налог телефона"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"одлазни"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"долазни"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"додај 1. позив"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"прекини 1. позив"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"додај 2. позив"</string>
+    <string name="set_call_active" msgid="248748409907478011">"подеси 2. позив као активан"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"прекини 2. позив"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-sv/strings.xml b/testapps/transactionalVoipApp/res/values-sv/strings.xml
new file mode 100644
index 0000000..586d1a4
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-sv/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Aktiviteten Test av transaktions-API"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Registrera telefonkonto"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"utgående"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"inkommande"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"lägg till samtal 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"lägg på samtal 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"lägg till samtal 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"ställ in samtal 2 som aktivt"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"lägg på samtal 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-sw/strings.xml b/testapps/transactionalVoipApp/res/values-sw/strings.xml
new file mode 100644
index 0000000..11a5a77
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-sw/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Shughuli za jaribio la API ya Uthibitishaji"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Sajili Akaunti ya Simu"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"simu unazopiga"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"simu zinazoingia"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"weka simu ya 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"kata simu ya 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"weka simu ya 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"piga simu ya 2"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"kata simu ya 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-ta/strings.xml b/testapps/transactionalVoipApp/res/values-ta/strings.xml
new file mode 100644
index 0000000..73d9944
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-ta/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Transactional API சோதனை செயல்பாடு"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"மொபைல் கணக்கைப் பதிவுசெய்தல்"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"வெளிச்செல்லும் அழைப்பு"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"உள்வரும் அழைப்பு"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"அழைப்பு 1ஐச் சேர்த்தல்"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"அழைப்பு 1ஐத் துண்டித்தல்"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"அழைப்பு 2ஐச் சேர்த்தல்"</string>
+    <string name="set_call_active" msgid="248748409907478011">"அழைப்பு 2 செயலில் உள்ளதாக அமைத்தல்"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"அழைப்பு 2ஐத் துண்டித்தல்"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-te/strings.xml b/testapps/transactionalVoipApp/res/values-te/strings.xml
new file mode 100644
index 0000000..708122a
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-te/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"లావాదేవీల API టెస్ట్ యాక్టివిటీ"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"ఫోన్ ఖాతాను రిజిస్టర్ చేయండి"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"అవుట్‌గోయింగ్"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"ఇన్‌కమింగ్"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"కాల్ 1ని జోడించండి"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"కాల్ 1ని డిస్కనెక్ట్ చేయండి"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"కాల్ 2ను జోడించండి"</string>
+    <string name="set_call_active" msgid="248748409907478011">"కాల్ 2ను యాక్టివ్‌గా సెట్ చేయండి"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"కాల్ 2ను డిస్కనెక్ట్ చేయండి"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-th/strings.xml b/testapps/transactionalVoipApp/res/values-th/strings.xml
new file mode 100644
index 0000000..207f291
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-th/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"กิจกรรมการทดสอบ API ธุรกรรม"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"ลงทะเบียนบัญชีของโทรศัพท์"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"สายโทรออก"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"สายเรียกเข้า"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"เพิ่มการโทร 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"ตัดสาย 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"เพิ่มการโทร 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"ตั้งค่าการโทร 2 เป็นใช้งานอยู่"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"ตัดสาย 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-tl/strings.xml b/testapps/transactionalVoipApp/res/values-tl/strings.xml
new file mode 100644
index 0000000..77f963b
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-tl/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Aktibidad ng pansubok na Transactional API"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Irehistro ang Phone Account"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"papalabas"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"incoming"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"magdagdag ng tawag 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"idiskonekta ang tawag 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"magdagdag ng tawag 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"itakdang aktibo ang tawag 2"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"idiskonekta ang tawag 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-tr/strings.xml b/testapps/transactionalVoipApp/res/values-tr/strings.xml
new file mode 100644
index 0000000..4d31191
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-tr/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Transactional API test etkinliği"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Telefon Hesabını Kaydet"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"giden"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"gelen"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"1. aramayı ekle"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"1. aramayı sonlandır"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"2. aramayı ekle"</string>
+    <string name="set_call_active" msgid="248748409907478011">"2. aramayı etkinleştir"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"2. aramayı sonlandır"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-uk/strings.xml b/testapps/transactionalVoipApp/res/values-uk/strings.xml
new file mode 100644
index 0000000..d1f5f1d
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-uk/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Тестування API підтвердження"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Зареєструвати обліковий запис телефона"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"вихідні дзвінки"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"вхідні дзвінки"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"додати дзвінок 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"завершити дзвінок 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"додати дзвінок 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"активувати дзвінок 2"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"завершити дзвінок 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-ur/strings.xml b/testapps/transactionalVoipApp/res/values-ur/strings.xml
new file mode 100644
index 0000000..8c14f04
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-ur/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"‏ٹرانزیکشنل API ٹیسٹ کی سرگرمی"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"فون کے اکاؤنٹ کو رجسٹر کریں"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"آؤٹ گوئنگ"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"اِن کمنگ"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"کال 1 کو شامل کریں"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"کال 1 کو منقطع کریں"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"کال 2 کو شامل کریں"</string>
+    <string name="set_call_active" msgid="248748409907478011">"کال 2 کو فعال کریں"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"کال 2 کو منقطع کریں"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-uz/strings.xml b/testapps/transactionalVoipApp/res/values-uz/strings.xml
new file mode 100644
index 0000000..4e212a1
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-uz/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Tranzaksiyaviy API sinovi faoliyati"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Telefon hisobini ro‘yxatdan o‘tkazish"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"chiquvchi"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"kiruvchi"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"chaqiruv qo‘shish 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"chaqiruvni uzish 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"chaqiruv qo‘shish 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"chaqiruv 2-ni faol qilish"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"chaqiruvni uzish 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-vi/strings.xml b/testapps/transactionalVoipApp/res/values-vi/strings.xml
new file mode 100644
index 0000000..2bcd65f
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-vi/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Hoạt động kiểm tra cho API Xác nhận trao đổi"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Đăng ký tài khoản điện thoại"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"cuộc gọi đi"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"cuộc gọi đến"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"thêm cuộc gọi 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"ngắt cuộc gọi 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"thêm cuộc gọi 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"đặt cuộc gọi 2 ở trạng thái đang diễn ra"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"ngắt cuộc gọi 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-zh-rCN/strings.xml b/testapps/transactionalVoipApp/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..512fdea
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-zh-rCN/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"事务性 API 测试活动"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"注册电话帐号"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"外拨电话"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"来电"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"添加通话 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"中断通话 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"添加通话 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"将通话 2 设置为通话中"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"中断通话 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-zh-rHK/strings.xml b/testapps/transactionalVoipApp/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..7729a28
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-zh-rHK/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Transactional API 測試活動"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"註冊電話帳戶"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"撥出"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"來電"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"新增通話 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"中斷通話 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"新增通話 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"將通話 2 設為進行中"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"中斷通話 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-zh-rTW/strings.xml b/testapps/transactionalVoipApp/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..90f8299
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-zh-rTW/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"交易 API 測試活動"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"註冊電話帳戶"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"撥出通話"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"來電"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"新增通話 1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"中斷通話 1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"新增通話 2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"將通話 2 設為啟用"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"中斷通話 2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values-zu/strings.xml b/testapps/transactionalVoipApp/res/values-zu/strings.xml
new file mode 100644
index 0000000..7689224
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-zu/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 
+  ~ Copyright (C) 2022 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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="app_name" msgid="2907804426411305091">"Umsebenzi wokuhlolwa kwe-Transactional API"</string>
+    <string name="register_phone_account" msgid="1920315963082350332">"Bhalisa I-akhawunti Yefoni"</string>
+    <string name="direction_outgoing" msgid="2535369086068422248">"okuphumayo"</string>
+    <string name="direction_incoming" msgid="3279919900558410227">"okungenayo"</string>
+    <string name="add_call_1" msgid="5825706540046010457">"engeza ikholi e-1"</string>
+    <string name="disconnect_call_1" msgid="2687960802565131403">"Nqamula ikholi e-1"</string>
+    <string name="add_call_2" msgid="6706005258041717434">"engeza ikholi yesi-2"</string>
+    <string name="set_call_active" msgid="248748409907478011">"setha ikholi yesi-2 ukuthi isebenze"</string>
+    <string name="disconnect_call_2" msgid="133113412102219516">"Nqamula ikholi yesi-2"</string>
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values/strings.xml b/testapps/transactionalVoipApp/res/values/strings.xml
new file mode 100644
index 0000000..038adc1
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<resources>
+    <string name="app_name">Transactional API test Activity</string>
+    <string name="register_phone_account">Register Phone Account</string>
+    <string name="direction_outgoing">outgoing</string>
+    <string name="direction_incoming">incoming</string>
+    <string name="add_call_1">add call 1</string>
+    <string name="disconnect_call_1">disconnect call 1</string>
+    <string name="add_call_2">add call 2</string>
+    <string name="set_call_active">set call 2 active</string>
+    <string name="disconnect_call_2">disconnect call 2</string>
+
+</resources>
\ No newline at end of file
diff --git a/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/MyVoipCall.java b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/MyVoipCall.java
new file mode 100644
index 0000000..690311e
--- /dev/null
+++ b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/MyVoipCall.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2022 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.server.telecom.transactionalVoipApp;
+
+import android.os.Bundle;
+import android.telecom.CallControlCallback;
+import android.telecom.CallEndpoint;
+import android.telecom.CallControl;
+import android.telecom.CallEventCallback;
+import android.telecom.DisconnectCause;
+import android.util.Log;
+
+import java.util.List;
+
+import androidx.annotation.NonNull;
+
+import java.util.function.Consumer;
+
+public class MyVoipCall implements CallControlCallback, CallEventCallback {
+
+    private static final String TAG = "MyVoipCall";
+    private final String mCallId;
+    CallControl mCallControl;
+
+    MyVoipCall(String id) {
+        mCallId = id;
+    }
+
+    public void onAddCallControl(@NonNull CallControl callControl) {
+        mCallControl = callControl;
+    }
+
+    @Override
+    public void onSetActive(@NonNull Consumer<Boolean> wasCompleted) {
+        Log.i(TAG, String.format("onSetActive: callId=[%s]", mCallId));
+        wasCompleted.accept(Boolean.TRUE);
+    }
+
+    @Override
+    public void onSetInactive(@NonNull Consumer<Boolean> wasCompleted) {
+        Log.i(TAG, String.format("onSetInactive: callId=[%s]", mCallId));
+        wasCompleted.accept(Boolean.TRUE);
+    }
+
+    @Override
+    public void onAnswer(int videoState, @NonNull Consumer<Boolean> wasCompleted) {
+        Log.i(TAG, String.format("onAnswer: callId=[%s]", mCallId));
+        wasCompleted.accept(Boolean.TRUE);
+    }
+
+    @Override
+    public void onDisconnect(@NonNull DisconnectCause cause,
+            @NonNull Consumer<Boolean> wasCompleted) {
+        Log.i(TAG, String.format("onDisconnect: callId=[%s]", mCallId));
+        wasCompleted.accept(Boolean.TRUE);
+    }
+
+    @Override
+    public void onCallStreamingStarted(@NonNull Consumer<Boolean> wasCompleted) {
+        Log.i(TAG, String.format("onCallStreamingStarted: callId=[%s]", mCallId));
+        wasCompleted.accept(Boolean.TRUE);
+    }
+
+    @Override
+    public void onCallStreamingFailed(int reason) {
+        Log.i(TAG, String.format("onCallStreamingFailed: id=[%s], reason=[%d]", mCallId, reason));
+    }
+
+    @Override
+    public void onEvent(String event, Bundle extras) {
+        Log.i(TAG, String.format("onEvent: id=[%s], event=[%s], extras=[%s]",
+                mCallId, event, extras));
+    }
+
+    @Override
+    public void onCallEndpointChanged(@NonNull CallEndpoint newCallEndpoint) {
+        Log.i(TAG, String.format("onCallEndpointChanged: endpoint=[%s]", newCallEndpoint));
+    }
+
+    @Override
+    public void onAvailableCallEndpointsChanged(
+            @NonNull List<CallEndpoint> availableEndpoints) {
+        Log.i(TAG, String.format("onAvailableCallEndpointsChanged: callId=[%s]", mCallId));
+        for (CallEndpoint endpoint : availableEndpoints) {
+            Log.i(TAG, String.format("endpoint=[%s]", endpoint));
+        }
+    }
+
+    @Override
+    public void onMuteStateChanged(boolean isMuted) {
+    }
+}
diff --git a/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/VoipAppMainActivity.java b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/VoipAppMainActivity.java
new file mode 100644
index 0000000..4e1ec4c
--- /dev/null
+++ b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/VoipAppMainActivity.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2022 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.server.telecom.transactionalVoipApp;
+
+import static android.telecom.CallAttributes.DIRECTION_INCOMING;
+import static android.telecom.CallAttributes.DIRECTION_OUTGOING;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.OutcomeReceiver;
+import android.telecom.CallAttributes;
+import android.telecom.CallControl;
+import android.telecom.CallException;
+import android.telecom.DisconnectCause;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+import android.util.Log;
+import android.view.View;
+import android.widget.ToggleButton;
+
+public class VoipAppMainActivity extends Activity {
+
+    private static final String TAG = "VoipAppMainActivity";
+    private static TelecomManager mTelecomManager;
+    private MyVoipCall mCall1;
+    private MyVoipCall mCall2;
+    private ToggleButton mCallDirectionButton;
+
+    PhoneAccountHandle handle = new PhoneAccountHandle(
+            new ComponentName("com.android.server.telecom.transactionalVoipApp",
+                    "com.android.server.telecom.transactionalVoipApp.VoipAppMainActivity"), "123");
+
+    PhoneAccount mPhoneAccount = PhoneAccount.builder(handle, "test label")
+            .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED |
+                    PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS).build();
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main_activity);
+
+        mTelecomManager = getSystemService(TelecomManager.class);
+        mCallDirectionButton = findViewById(R.id.callDirectionButton);
+
+        // register account
+        findViewById(R.id.registerButton).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mTelecomManager.registerPhoneAccount(mPhoneAccount);
+            }
+        });
+
+        // call 1 buttons
+        findViewById(R.id.add_call_1_button).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Bundle extras = new Bundle();
+                extras.putString("testKey", "testValue");
+                mCall1 = new MyVoipCall("1");
+                addCall(mCall1, true);
+            }
+        });
+
+        findViewById(R.id.disconnect_call_1_button).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                disconnectCall(mCall1);
+            }
+        });
+
+
+        //call 2 buttons
+        findViewById(R.id.add_call_2_button).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Bundle extras = new Bundle();
+                extras.putString("call2extraKey", "call2Value");
+                mCall2 = new MyVoipCall("2");
+                addCall(mCall2, false);
+            }
+        });
+
+        findViewById(R.id.set_call_2_active_button).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                setCallActive(mCall2);
+            }
+        });
+
+        findViewById(R.id.disconnect_call_2_button).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                disconnectCall(mCall2);
+            }
+        });
+    }
+
+    private void addCall(MyVoipCall call, boolean setActive) {
+        int direction = (mCallDirectionButton.isChecked() ? DIRECTION_INCOMING
+                : DIRECTION_OUTGOING);
+
+        CallAttributes callAttributes = new CallAttributes.Builder(handle, direction, "Alan Turing",
+                Uri.fromParts("tel", "abc", "123")).build();
+
+        mTelecomManager.addCall(callAttributes, Runnable::run,
+                new OutcomeReceiver<CallControl, CallException>() {
+                    @Override
+                    public void onResult(CallControl callControl) {
+                        Log.i(TAG, "addCall: onResult: callback fired");
+                        call.onAddCallControl(callControl);
+                        if (setActive) {
+                            setCallActive(call);
+                        }
+                    }
+
+                    @Override
+                    public void onError(CallException exception) {
+
+                    }
+                },
+                call, call);
+    }
+
+    private void setCallActive(MyVoipCall call) {
+        call.mCallControl.setActive(Runnable::run, new OutcomeReceiver<Void, CallException>() {
+            @Override
+            public void onResult(Void result) {
+                Log.i(TAG, "setCallActive: onResult");
+            }
+
+            @Override
+            public void onError(CallException exception) {
+                Log.i(TAG, "setCallActive: onError");
+            }
+        });
+    }
+
+    private void disconnectCall(MyVoipCall call) {
+        call.mCallControl.disconnect(new DisconnectCause(DisconnectCause.LOCAL), Runnable::run,
+                new OutcomeReceiver<Void, CallException>() {
+                    @Override
+                    public void onResult(Void result) {
+                        Log.i(TAG, "disconnectCall: onResult");
+                    }
+
+                    @Override
+                    public void onError(CallException exception) {
+                        Log.i(TAG, "disconnectCall: onError");
+                    }
+                });
+    }
+}
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index e8c69d4..4ca6030 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -21,13 +21,14 @@
 
     <uses-sdk
         android:minSdkVersion="23"
-        android:targetSdkVersion="23" />
+        android:targetSdkVersion="33" />
 
+    <uses-permission android:name="android.permission.READ_DEVICE_CONFIG"/>
     <!-- TODO: Needed because we call BluetoothAdapter.getDefaultAdapter() statically, and
          BluetoothAdapter is a final class. -->
     <uses-permission android:name="android.permission.BLUETOOTH" />
+    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
     <uses-permission android:name="android.permission.BLUETOOTH_PRIVILEGED" />
-
     <!-- TODO: Needed because we call ActivityManager.getCurrentUser() statically. -->
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
     <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
@@ -43,6 +44,9 @@
     <!-- Used to access PlatformCompat APIs -->
     <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" />
     <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE" />
+    
+    <!-- Used to register NotificationListenerService -->
+    <uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
 
     <application android:label="@string/app_name"
                  android:debuggable="true">
diff --git a/tests/src/com/android/server/telecom/tests/BasicCallTests.java b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
index 049a501..453450d 100644
--- a/tests/src/com/android/server/telecom/tests/BasicCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
@@ -87,6 +87,7 @@
  */
 @RunWith(JUnit4.class)
 public class BasicCallTests extends TelecomSystemTest {
+    private static final String CALLING_PACKAGE = BasicCallTests.class.getPackageName();
     private static final String TEST_BUNDLE_KEY = "android.telecom.extra.TEST";
     private static final String TEST_EVENT = "android.telecom.event.TEST";
 
@@ -327,7 +328,7 @@
                 TelecomManager.EXTRA_INCOMING_CALL_ADDRESS,
                 Uri.fromParts(PhoneAccount.SCHEME_TEL, phoneNumber, null));
         mTelecomSystem.getTelecomServiceImpl().getBinder()
-                .addNewIncomingCall(mPhoneAccountA0.getAccountHandle(), extras);
+                .addNewIncomingCall(mPhoneAccountA0.getAccountHandle(), extras, CALLING_PACKAGE);
 
         waitForHandlerAction(mConnectionServiceFixtureA.mConnectionServiceDelegate.getHandler(),
                 TEST_TIMEOUT);
@@ -392,7 +393,7 @@
                 TelecomManager.EXTRA_INCOMING_CALL_ADDRESS,
                 Uri.fromParts(PhoneAccount.SCHEME_TEL, "650-555-1212", null));
         mTelecomSystem.getTelecomServiceImpl().getBinder()
-                .addNewIncomingCall(mPhoneAccountA0.getAccountHandle(), extras);
+                .addNewIncomingCall(mPhoneAccountA0.getAccountHandle(), extras, CALLING_PACKAGE);
 
         waitForHandlerAction(mConnectionServiceFixtureA.mConnectionServiceDelegate.getHandler(),
                 TEST_TIMEOUT);
@@ -442,7 +443,7 @@
                 TelecomManager.EXTRA_INCOMING_CALL_ADDRESS,
                 Uri.fromParts(PhoneAccount.SCHEME_TEL, "650-555-1212", null));
         mTelecomSystem.getTelecomServiceImpl().getBinder()
-                .addNewIncomingCall(mPhoneAccountA0.getAccountHandle(), extras);
+                .addNewIncomingCall(mPhoneAccountA0.getAccountHandle(), extras, CALLING_PACKAGE);
 
         waitForHandlerAction(mConnectionServiceFixtureA.mConnectionServiceDelegate.getHandler(),
                 TEST_TIMEOUT);
@@ -494,7 +495,7 @@
                 TelecomManager.EXTRA_INCOMING_CALL_ADDRESS,
                 Uri.fromParts(PhoneAccount.SCHEME_TEL, phoneNumber, null));
         mTelecomSystem.getTelecomServiceImpl().getBinder()
-                .addNewIncomingCall(mPhoneAccountA0.getAccountHandle(), extras);
+                .addNewIncomingCall(mPhoneAccountA0.getAccountHandle(), extras, CALLING_PACKAGE);
 
         waitForHandlerAction(mConnectionServiceFixtureA.mConnectionServiceDelegate.getHandler(),
                 TEST_TIMEOUT);
diff --git a/tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java b/tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java
new file mode 100644
index 0000000..7e197fe
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java
@@ -0,0 +1,867 @@
+/*
+ * Copyright (C) 2022 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.server.telecom.tests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.telecom.DisconnectCause;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+
+import com.android.server.telecom.AnomalyReporterAdapter;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallAnomalyWatchdog;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.CallerInfoLookupHelper;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.ClockProxy;
+import com.android.server.telecom.ConnectionServiceWrapper;
+import com.android.server.telecom.EmergencyCallDiagnosticLogger;
+import com.android.server.telecom.PhoneAccountRegistrar;
+import com.android.server.telecom.PhoneNumberUtilsAdapter;
+import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.Timeouts;
+import com.android.server.telecom.ui.ToastFactory;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+
+@RunWith(JUnit4.class)
+public class CallAnomalyWatchdogTest extends TelecomTestCase {
+    private static final ComponentName COMPONENT_NAME_1 = ComponentName
+            .unflattenFromString("com.foo/.Blah");
+    private static final PhoneAccountHandle SIM_1_HANDLE = new PhoneAccountHandle(
+            COMPONENT_NAME_1, "Sim1");
+    private static final PhoneAccount SIM_1_ACCOUNT = new PhoneAccount.
+            Builder(SIM_1_HANDLE, "Sim1")
+            .setCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION
+                    | PhoneAccount.CAPABILITY_CALL_PROVIDER)
+            .setIsEnabled(true)
+            .build();
+
+    private final static long TEST_VOIP_TRANSITORY_MILLIS = 100L;
+    private final static long TEST_VOIP_EMERGENCY_TRANSITORY_MILLIS = 150L;
+    private final static long TEST_NON_VOIP_TRANSITORY_MILLIS = 200L;
+    private final static long TEST_NON_VOIP_EMERGENCY_TRANSITORY_MILLIS = 250L;
+    private final static long TEST_VOIP_INTERMEDIATE_MILLIS = 300L;
+    private final static long TEST_VOIP_EMERGENCY_INTERMEDIATE_MILLIS = 350L;
+    private final static long TEST_NON_VOIP_INTERMEDIATE_MILLIS = 400L;
+    private final static long TEST_NON_VOIP_EMERGENCY_INTERMEDIATE_MILLIS = 450L;
+
+    private CallAnomalyWatchdog mCallAnomalyWatchdog;
+    private TestScheduledExecutorService mTestScheduledExecutorService;
+    private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
+    @Mock private Timeouts.Adapter mTimeouts;
+    @Mock private CallsManager mMockCallsManager;
+    @Mock private CallerInfoLookupHelper mMockCallerInfoLookupHelper;
+    @Mock private PhoneAccountRegistrar mMockPhoneAccountRegistrar;
+    @Mock private ClockProxy mMockClockProxy;
+    @Mock private ToastFactory mMockToastProxy;
+    @Mock private PhoneNumberUtilsAdapter mMockPhoneNumberUtilsAdapter;
+    @Mock private ConnectionServiceWrapper mMockConnectionService;
+    @Mock private AnomalyReporterAdapter mAnomalyReporterAdapter;
+
+    @Mock private EmergencyCallDiagnosticLogger mMockEmergencyCallDiagnosticLogger;
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        doReturn(mMockCallerInfoLookupHelper).when(mMockCallsManager).getCallerInfoLookupHelper();
+        doReturn(mMockPhoneAccountRegistrar).when(mMockCallsManager).getPhoneAccountRegistrar();
+        doReturn(SIM_1_ACCOUNT).when(mMockPhoneAccountRegistrar).getPhoneAccountUnchecked(
+                eq(SIM_1_HANDLE));
+        mTestScheduledExecutorService = new TestScheduledExecutorService();
+
+        when(mTimeouts.getVoipCallTransitoryStateTimeoutMillis()).
+                thenReturn(TEST_VOIP_TRANSITORY_MILLIS);
+        when(mTimeouts.getVoipEmergencyCallTransitoryStateTimeoutMillis()).
+                thenReturn(TEST_VOIP_EMERGENCY_TRANSITORY_MILLIS);
+        when(mTimeouts.getNonVoipCallTransitoryStateTimeoutMillis()).
+                thenReturn(TEST_NON_VOIP_TRANSITORY_MILLIS);
+        when(mTimeouts.getNonVoipEmergencyCallTransitoryStateTimeoutMillis()).
+                thenReturn(TEST_NON_VOIP_EMERGENCY_TRANSITORY_MILLIS);
+        when(mTimeouts.getVoipCallIntermediateStateTimeoutMillis()).
+                thenReturn(TEST_VOIP_INTERMEDIATE_MILLIS);
+        when(mTimeouts.getVoipEmergencyCallIntermediateStateTimeoutMillis()).
+                thenReturn(TEST_VOIP_EMERGENCY_INTERMEDIATE_MILLIS);
+        when(mTimeouts.getNonVoipCallIntermediateStateTimeoutMillis()).
+                thenReturn(TEST_NON_VOIP_INTERMEDIATE_MILLIS);
+        when(mTimeouts.getNonVoipEmergencyCallIntermediateStateTimeoutMillis()).
+                thenReturn(TEST_NON_VOIP_EMERGENCY_INTERMEDIATE_MILLIS);
+
+        when(mMockClockProxy.elapsedRealtime()).thenReturn(0L);
+        doReturn(new ComponentName(mContext, CallTest.class))
+                .when(mMockConnectionService).getComponentName();
+        mCallAnomalyWatchdog = new CallAnomalyWatchdog(mTestScheduledExecutorService, mLock,
+                mTimeouts, mMockClockProxy, mMockEmergencyCallDiagnosticLogger);
+        mCallAnomalyWatchdog.setAnomalyReporterAdapter(mAnomalyReporterAdapter);
+    }
+
+    @Override
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    /**
+     * Helper function that setups the call being tested.
+     */
+    private Call setupCallHelper(int callState, boolean isCreateConnectionComplete,
+            ConnectionServiceWrapper service, boolean isVoipAudioMode, boolean isEmergencyCall) {
+        Call call = getCall();
+        call.setState(callState, "foo");
+        call.setIsCreateConnectionComplete(isCreateConnectionComplete);
+        if (service != null) call.setConnectionService(service);
+        call.setIsVoipAudioMode(isVoipAudioMode);
+        call.setIsEmergencyCall(isEmergencyCall);
+        mCallAnomalyWatchdog.onCallAdded(call);
+        return call;
+    }
+
+    /**
+     * Test that the anomaly call state class correctly reports whether the state is transitory or
+     * not for the purposes of the call anomaly watchdog.
+     */
+    @Test
+    public void testAnomalyCallStateIsTransitory() {
+        // When connection creation isn't complete, most states are transitory from the anomaly
+        // tracker's point of view.
+        assertTrue(new CallAnomalyWatchdog.WatchdogCallState(CallState.NEW,
+                false /* isCreateConnectionComplete */, 0L).isInTransitoryState());
+        assertTrue(new CallAnomalyWatchdog.WatchdogCallState(CallState.CONNECTING,
+                false /* isCreateConnectionComplete */, 0L).isInTransitoryState());
+        assertTrue(new CallAnomalyWatchdog.WatchdogCallState(CallState.DIALING,
+                false /* isCreateConnectionComplete */, 0L).isInTransitoryState());
+        assertTrue(new CallAnomalyWatchdog.WatchdogCallState(CallState.RINGING,
+                false /* isCreateConnectionComplete */, 0L).isInTransitoryState());
+        assertTrue(new CallAnomalyWatchdog.WatchdogCallState(CallState.ACTIVE,
+                false /* isCreateConnectionComplete */, 0L).isInTransitoryState());
+        assertTrue(new CallAnomalyWatchdog.WatchdogCallState(CallState.ON_HOLD,
+                false /* isCreateConnectionComplete */, 0L).isInTransitoryState());
+        assertTrue(new CallAnomalyWatchdog.WatchdogCallState(CallState.DISCONNECTED,
+                false /* isCreateConnectionComplete */, 0L).isInTransitoryState());
+        assertTrue(new CallAnomalyWatchdog.WatchdogCallState(CallState.ABORTED,
+                false /* isCreateConnectionComplete */, 0L).isInTransitoryState());
+        assertTrue(new CallAnomalyWatchdog.WatchdogCallState(CallState.DISCONNECTING,
+                false /* isCreateConnectionComplete */, 0L).isInTransitoryState());
+        assertTrue(new CallAnomalyWatchdog.WatchdogCallState(CallState.PULLING,
+                false /* isCreateConnectionComplete */, 0L).isInTransitoryState());
+        assertTrue(new CallAnomalyWatchdog.WatchdogCallState(CallState.ANSWERED,
+                false /* isCreateConnectionComplete */, 0L).isInTransitoryState());
+        assertTrue(new CallAnomalyWatchdog.WatchdogCallState(CallState.AUDIO_PROCESSING,
+                false /* isCreateConnectionComplete */, 0L).isInTransitoryState());
+        assertTrue(new CallAnomalyWatchdog.WatchdogCallState(CallState.SIMULATED_RINGING,
+                false /* isCreateConnectionComplete */, 0L).isInTransitoryState());
+        // When create connection is complete, these few are considered to be transitory.
+        assertTrue(new CallAnomalyWatchdog.WatchdogCallState(CallState.NEW,
+                true /* isCreateConnectionComplete */, 0L).isInTransitoryState());
+        assertTrue(new CallAnomalyWatchdog.WatchdogCallState(CallState.CONNECTING,
+                true /* isCreateConnectionComplete */, 0L).isInTransitoryState());
+        assertTrue(new CallAnomalyWatchdog.WatchdogCallState(CallState.DISCONNECTING,
+                true /* isCreateConnectionComplete */, 0L).isInTransitoryState());
+        assertTrue(new CallAnomalyWatchdog.WatchdogCallState(CallState.ANSWERED,
+                true /* isCreateConnectionComplete */, 0L).isInTransitoryState());
+
+        // These are never considered transitory
+        assertFalse(new CallAnomalyWatchdog.WatchdogCallState(CallState.SELECT_PHONE_ACCOUNT,
+                false /* isCreateConnectionComplete */, 0L).isInTransitoryState());
+        assertFalse(new CallAnomalyWatchdog.WatchdogCallState(CallState.SELECT_PHONE_ACCOUNT,
+                true /* isCreateConnectionComplete */, 0L).isInTransitoryState());
+        assertFalse(new CallAnomalyWatchdog.WatchdogCallState(CallState.DIALING,
+                true /* isCreateConnectionComplete */, 0L).isInTransitoryState());
+        assertFalse(new CallAnomalyWatchdog.WatchdogCallState(CallState.RINGING,
+                true /* isCreateConnectionComplete */, 0L).isInTransitoryState());
+        assertFalse(new CallAnomalyWatchdog.WatchdogCallState(CallState.ACTIVE,
+                true /* isCreateConnectionComplete */, 0L).isInTransitoryState());
+        assertFalse(new CallAnomalyWatchdog.WatchdogCallState(CallState.ON_HOLD,
+                true /* isCreateConnectionComplete */, 0L).isInTransitoryState());
+        assertFalse(new CallAnomalyWatchdog.WatchdogCallState(CallState.DISCONNECTED,
+                true /* isCreateConnectionComplete */, 0L).isInTransitoryState());
+        assertFalse(new CallAnomalyWatchdog.WatchdogCallState(CallState.ABORTED,
+                true /* isCreateConnectionComplete */, 0L).isInTransitoryState());
+        assertFalse(new CallAnomalyWatchdog.WatchdogCallState(CallState.PULLING,
+                true /* isCreateConnectionComplete */, 0L).isInTransitoryState());
+        assertFalse(new CallAnomalyWatchdog.WatchdogCallState(CallState.AUDIO_PROCESSING,
+                true /* isCreateConnectionComplete */, 0L).isInTransitoryState());
+        assertFalse(new CallAnomalyWatchdog.WatchdogCallState(CallState.SIMULATED_RINGING,
+                true /* isCreateConnectionComplete */, 0L).isInTransitoryState());
+    }
+
+    /**
+     * Test that the anomaly call state class correctly reports whether the state is intermediate or
+     * not for the purposes of the call anomaly watchdog.
+     */
+    @Test
+    public void testAnomalyCallStateIsIntermediate() {
+        // When connection creation isn't complete, most states are not intermediate from
+        // the anomaly tracker's point of view.
+        assertFalse(new CallAnomalyWatchdog.WatchdogCallState(CallState.NEW,
+                false /* isCreateConnectionComplete */, 0L).isInIntermediateState());
+        assertFalse(new CallAnomalyWatchdog.WatchdogCallState(CallState.CONNECTING,
+                false /* isCreateConnectionComplete */, 0L).isInIntermediateState());
+        assertFalse(new CallAnomalyWatchdog.WatchdogCallState(CallState.SELECT_PHONE_ACCOUNT,
+                false /* isCreateConnectionComplete */, 0L).isInIntermediateState());
+        assertFalse(new CallAnomalyWatchdog.WatchdogCallState(CallState.DIALING,
+                false /* isCreateConnectionComplete */, 0L).isInIntermediateState());
+        assertFalse(new CallAnomalyWatchdog.WatchdogCallState(CallState.RINGING,
+                false /* isCreateConnectionComplete */, 0L).isInIntermediateState());
+        assertFalse(new CallAnomalyWatchdog.WatchdogCallState(CallState.ACTIVE,
+                false /* isCreateConnectionComplete */, 0L).isInIntermediateState());
+        assertFalse(new CallAnomalyWatchdog.WatchdogCallState(CallState.ON_HOLD,
+                false /* isCreateConnectionComplete */, 0L).isInIntermediateState());
+        assertFalse(new CallAnomalyWatchdog.WatchdogCallState(CallState.DISCONNECTED,
+                false /* isCreateConnectionComplete */, 0L).isInIntermediateState());
+        assertFalse(new CallAnomalyWatchdog.WatchdogCallState(CallState.ABORTED,
+                false /* isCreateConnectionComplete */, 0L).isInIntermediateState());
+        assertFalse(new CallAnomalyWatchdog.WatchdogCallState(CallState.DISCONNECTING,
+                false /* isCreateConnectionComplete */, 0L).isInIntermediateState());
+        assertFalse(new CallAnomalyWatchdog.WatchdogCallState(CallState.PULLING,
+                false /* isCreateConnectionComplete */, 0L).isInIntermediateState());
+        assertFalse(new CallAnomalyWatchdog.WatchdogCallState(CallState.ANSWERED,
+                false /* isCreateConnectionComplete */, 0L).isInIntermediateState());
+        assertFalse(new CallAnomalyWatchdog.WatchdogCallState(CallState.AUDIO_PROCESSING,
+                false /* isCreateConnectionComplete */, 0L).isInIntermediateState());
+        assertFalse(new CallAnomalyWatchdog.WatchdogCallState(CallState.SIMULATED_RINGING,
+                false /* isCreateConnectionComplete */, 0L).isInIntermediateState());
+
+        // If it is not in DIALING and RINGING state, it is not considered as an intermediate state.
+        assertFalse(new CallAnomalyWatchdog.WatchdogCallState(CallState.NEW,
+                true /* isCreateConnectionComplete */, 0L).isInIntermediateState());
+        assertFalse(new CallAnomalyWatchdog.WatchdogCallState(CallState.CONNECTING,
+                true /* isCreateConnectionComplete */, 0L).isInIntermediateState());
+        assertFalse(new CallAnomalyWatchdog.WatchdogCallState(CallState.SELECT_PHONE_ACCOUNT,
+                true /* isCreateConnectionComplete */, 0L).isInIntermediateState());
+        assertFalse(new CallAnomalyWatchdog.WatchdogCallState(CallState.ACTIVE,
+                true /* isCreateConnectionComplete */, 0L).isInIntermediateState());
+        assertFalse(new CallAnomalyWatchdog.WatchdogCallState(CallState.ON_HOLD,
+                true /* isCreateConnectionComplete */, 0L).isInIntermediateState());
+        assertFalse(new CallAnomalyWatchdog.WatchdogCallState(CallState.DISCONNECTED,
+                true /* isCreateConnectionComplete */, 0L).isInIntermediateState());
+        assertFalse(new CallAnomalyWatchdog.WatchdogCallState(CallState.ABORTED,
+                true /* isCreateConnectionComplete */, 0L).isInIntermediateState());
+        assertFalse(new CallAnomalyWatchdog.WatchdogCallState(CallState.DISCONNECTING,
+                true /* isCreateConnectionComplete */, 0L).isInIntermediateState());
+        assertFalse(new CallAnomalyWatchdog.WatchdogCallState(CallState.PULLING,
+                true /* isCreateConnectionComplete */, 0L).isInIntermediateState());
+        assertFalse(new CallAnomalyWatchdog.WatchdogCallState(CallState.ANSWERED,
+                true /* isCreateConnectionComplete */, 0L).isInIntermediateState());
+        assertFalse(new CallAnomalyWatchdog.WatchdogCallState(CallState.AUDIO_PROCESSING,
+                true /* isCreateConnectionComplete */, 0L).isInIntermediateState());
+        assertFalse(new CallAnomalyWatchdog.WatchdogCallState(CallState.SIMULATED_RINGING,
+                true /* isCreateConnectionComplete */, 0L).isInIntermediateState());
+
+        // These are considered as an intermediate state.
+        assertTrue(new CallAnomalyWatchdog.WatchdogCallState(CallState.DIALING,
+                true /* isCreateConnectionComplete */, 0L).isInIntermediateState());
+        assertTrue(new CallAnomalyWatchdog.WatchdogCallState(CallState.RINGING,
+                true /* isCreateConnectionComplete */, 0L).isInIntermediateState());
+    }
+
+    /**
+     * Emulate the case where a new incoming VoIP call is added to the watchdog.
+     * CallsManager creates calls in a ringing state before they're even created by the underlying
+     * ConnectionService.  The call is added by the connection service before the timeout expires,
+     * so we verify that the call does not get disconnected.
+     */
+     @Test
+    public void testAddVoipRingingCall() {
+        Call call = setupCallHelper(CallState.RINGING, false, null, true, false);
+
+        // Newly created call which hasn't been added; should schedule timeout.
+        assertEquals(1, mTestScheduledExecutorService.getNumberOfScheduledRunnables());
+        assertTrue(mTestScheduledExecutorService.
+                isRunnableScheduledAtTime(TEST_VOIP_TRANSITORY_MILLIS));
+        assertEquals(1, mCallAnomalyWatchdog.getNumberOfScheduledTimeouts());
+
+        // Gets added to connection service; this moves it to an intermediate state,
+        // so timeouts should be scheduled at this point.
+        call.setIsCreateConnectionComplete(true);
+        call.setConnectionService(mMockConnectionService);
+        mCallAnomalyWatchdog.onCallAdded(call);
+
+        assertEquals(1, mTestScheduledExecutorService.getNumberOfScheduledRunnables());
+        assertTrue(mTestScheduledExecutorService.
+                isRunnableScheduledAtTime(TEST_VOIP_INTERMEDIATE_MILLIS));
+        assertEquals(1, mCallAnomalyWatchdog.getNumberOfScheduledTimeouts());
+
+        // Move the clock forward; we'll confirm that no timeout took place.
+        when(mMockClockProxy.elapsedRealtime()).thenReturn(TEST_VOIP_INTERMEDIATE_MILLIS + 1);
+        mTestScheduledExecutorService.advanceTime(TEST_VOIP_INTERMEDIATE_MILLIS + 1);
+        // Should still be ringing.
+        assertEquals(CallState.RINGING, call.getState());
+    }
+
+    /**
+     * Emulate the case where a new incoming VoIP emergency call is added to the watchdog.
+     * CallsManager creates calls in a ringing state before they're even created by the underlying
+     * ConnectionService.  The call is added by the connection service before the timeout expires,
+     * so we verify that the call does not get disconnected.
+     */
+    @Test
+    public void testAddVoipEmergencyRingingCall() {
+        Call call = setupCallHelper(CallState.RINGING, false, null, true, true);
+
+        // Newly created call which hasn't been added; should schedule timeout.
+        assertEquals(1, mTestScheduledExecutorService.getNumberOfScheduledRunnables());
+        assertTrue(mTestScheduledExecutorService.
+                isRunnableScheduledAtTime(TEST_VOIP_EMERGENCY_TRANSITORY_MILLIS));
+        assertEquals(1, mCallAnomalyWatchdog.getNumberOfScheduledTimeouts());
+
+        // Gets added to connection service; this moves it to an intermediate state,
+        // so timeouts should be scheduled at this point.
+        call.setIsCreateConnectionComplete(true);
+        call.setConnectionService(mMockConnectionService);
+        mCallAnomalyWatchdog.onCallAdded(call);
+
+        assertEquals(1, mTestScheduledExecutorService.getNumberOfScheduledRunnables());
+        assertTrue(mTestScheduledExecutorService.
+                isRunnableScheduledAtTime(TEST_VOIP_EMERGENCY_INTERMEDIATE_MILLIS));
+        assertEquals(1, mCallAnomalyWatchdog.getNumberOfScheduledTimeouts());
+
+        // Move the clock forward; we'll confirm that no timeout took place.
+        when(mMockClockProxy.elapsedRealtime()).
+                thenReturn(TEST_VOIP_EMERGENCY_INTERMEDIATE_MILLIS + 1);
+        mTestScheduledExecutorService.advanceTime(TEST_VOIP_EMERGENCY_INTERMEDIATE_MILLIS + 1);
+        // Should still be ringing.
+        assertEquals(CallState.RINGING, call.getState());
+    }
+
+    /**
+     * Emulate the case where a new incoming non-VoIP call is added to the watchdog.
+     * CallsManager creates calls in a ringing state before they're even created by the underlying
+     * ConnectionService.  The call is added by the connection service before the timeout expires,
+     * so we verify that the call does not get disconnected.
+     */
+    @Test
+    public void testAddNonVoipRingingCall() {
+        Call call = setupCallHelper(CallState.RINGING, false, null, false, false);
+
+        // Newly created call which hasn't been added; should schedule timeout.
+        assertEquals(1, mTestScheduledExecutorService.getNumberOfScheduledRunnables());
+        assertTrue(mTestScheduledExecutorService.
+                isRunnableScheduledAtTime(TEST_NON_VOIP_TRANSITORY_MILLIS));
+        assertEquals(1, mCallAnomalyWatchdog.getNumberOfScheduledTimeouts());
+
+        // Gets added to connection service; this moves it to an intermediate state,
+        // so timeouts should be scheduled at this point.
+        call.setIsCreateConnectionComplete(true);
+        call.setConnectionService(mMockConnectionService);
+        mCallAnomalyWatchdog.onCallAdded(call);
+
+        assertEquals(1, mTestScheduledExecutorService.getNumberOfScheduledRunnables());
+        assertTrue(mTestScheduledExecutorService.
+                isRunnableScheduledAtTime(TEST_NON_VOIP_INTERMEDIATE_MILLIS));
+        assertEquals(1, mCallAnomalyWatchdog.getNumberOfScheduledTimeouts());
+
+        // Move the clock forward; we'll confirm that no timeout took place.
+        when(mMockClockProxy.elapsedRealtime()).
+                thenReturn(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);
+        mTestScheduledExecutorService.advanceTime(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);
+        // Should still be ringing.
+        assertEquals(CallState.RINGING, call.getState());
+    }
+
+    /**
+     * Emulate the case where a new incoming non-VoIP emergency call is added to the watchdog.
+     * CallsManager creates calls in a ringing state before they're even created by the underlying
+     * ConnectionService.  The call is added by the connection service before the timeout expires,
+     * so we verify that the call does not get disconnected.
+     */
+    @Test
+    public void testAddNonVoipEmergencyRingingCall() {
+        Call call = setupCallHelper(CallState.RINGING, false, null, false, true);
+
+        // Newly created call which hasn't been added; should schedule timeout.
+        assertEquals(1, mTestScheduledExecutorService.getNumberOfScheduledRunnables());
+        assertTrue(mTestScheduledExecutorService.
+                isRunnableScheduledAtTime(TEST_NON_VOIP_EMERGENCY_TRANSITORY_MILLIS));
+        assertEquals(1, mCallAnomalyWatchdog.getNumberOfScheduledTimeouts());
+
+        // Gets added to connection service; this moves it to an intermediate state,
+        // so timeouts should be scheduled at this point.
+        call.setIsCreateConnectionComplete(true);
+        call.setConnectionService(mMockConnectionService);
+        mCallAnomalyWatchdog.onCallAdded(call);
+
+        assertEquals(1, mTestScheduledExecutorService.getNumberOfScheduledRunnables());
+        assertTrue(mTestScheduledExecutorService.
+                isRunnableScheduledAtTime(TEST_NON_VOIP_EMERGENCY_INTERMEDIATE_MILLIS));
+        assertEquals(1, mCallAnomalyWatchdog.getNumberOfScheduledTimeouts());
+
+        // Move the clock forward; we'll confirm that no timeout took place.
+        when(mMockClockProxy.elapsedRealtime()).
+                thenReturn(TEST_NON_VOIP_EMERGENCY_INTERMEDIATE_MILLIS + 1);
+        mTestScheduledExecutorService.advanceTime(TEST_NON_VOIP_EMERGENCY_INTERMEDIATE_MILLIS + 1);
+        // Should still be ringing.
+        assertEquals(CallState.RINGING, call.getState());
+    }
+
+    /**
+     * Emulate the case where a new incoming VoIP call is added to the watchdog.
+     * In this case, the ConnectionService doesn't respond promptly and the timeout will fire.
+     */
+    @Test
+    public void testAddVoipRingingCallTimeoutWithoutConnection() {
+        setupCallHelper(CallState.RINGING, false, null, true, false);
+
+        // Newly created call which hasn't been added; should schedule timeout.
+        assertEquals(1, mTestScheduledExecutorService.getNumberOfScheduledRunnables());
+        assertTrue(mTestScheduledExecutorService.
+                isRunnableScheduledAtTime(TEST_VOIP_TRANSITORY_MILLIS));
+        assertEquals(1, mCallAnomalyWatchdog.getNumberOfScheduledTimeouts());
+
+        // Move the clock to fire the timeout.
+        when(mMockClockProxy.elapsedRealtime()).thenReturn(TEST_VOIP_TRANSITORY_MILLIS + 1);
+        mTestScheduledExecutorService.advanceTime(TEST_VOIP_TRANSITORY_MILLIS + 1);
+
+        // No timeouts should be pending at this point since the timeout fired.
+        assertEquals(0, mTestScheduledExecutorService.getNumberOfScheduledRunnables());
+        assertEquals(0, mCallAnomalyWatchdog.getNumberOfScheduledTimeouts());
+    }
+
+    /**
+     * Emulate the case where a new incoming VoIP emergency call is added to the watchdog.
+     * In this case, the ConnectionService doesn't respond promptly and the timeout will fire.
+     */
+    @Test
+    public void testAddVoipEmergencyRingingCallTimeoutWithoutConnection() {
+        setupCallHelper(CallState.RINGING, false, null, true, true);
+
+        // Newly created call which hasn't been added; should schedule timeout.
+        assertEquals(1, mTestScheduledExecutorService.getNumberOfScheduledRunnables());
+        assertTrue(mTestScheduledExecutorService.
+                isRunnableScheduledAtTime(TEST_VOIP_EMERGENCY_TRANSITORY_MILLIS));
+        assertEquals(1, mCallAnomalyWatchdog.getNumberOfScheduledTimeouts());
+
+        // Move the clock to fire the timeout.
+        when(mMockClockProxy.elapsedRealtime()).thenReturn(TEST_VOIP_EMERGENCY_TRANSITORY_MILLIS +
+                1);
+        mTestScheduledExecutorService.advanceTime(TEST_VOIP_EMERGENCY_TRANSITORY_MILLIS + 1);
+
+        // No timeouts should be pending at this point since the timeout fired.
+        assertEquals(0, mTestScheduledExecutorService.getNumberOfScheduledRunnables());
+        assertEquals(0, mCallAnomalyWatchdog.getNumberOfScheduledTimeouts());
+    }
+
+    /**
+     * Emulate the case where a new incoming non-VoIP call is added to the watchdog.
+     * In this case, the ConnectionService doesn't respond promptly and the timeout will fire.
+     */
+    @Test
+    public void testAddNonVoipRingingCallTimeoutWithoutConnection() {
+        setupCallHelper(CallState.RINGING, false, null, false, false);;
+
+        // Newly created call which hasn't been added; should schedule timeout.
+        assertEquals(1, mTestScheduledExecutorService.getNumberOfScheduledRunnables());
+        assertTrue(mTestScheduledExecutorService.
+                isRunnableScheduledAtTime(TEST_NON_VOIP_TRANSITORY_MILLIS));
+        assertEquals(1, mCallAnomalyWatchdog.getNumberOfScheduledTimeouts());
+
+        // Move the clock to fire the timeout.
+        when(mMockClockProxy.elapsedRealtime()).thenReturn(TEST_NON_VOIP_TRANSITORY_MILLIS + 1);
+        mTestScheduledExecutorService.advanceTime(TEST_NON_VOIP_TRANSITORY_MILLIS + 1);
+
+        // No timeouts should be pending at this point since the timeout fired.
+        assertEquals(0, mTestScheduledExecutorService.getNumberOfScheduledRunnables());
+        assertEquals(0, mCallAnomalyWatchdog.getNumberOfScheduledTimeouts());
+    }
+
+    /**
+     * Emulate the case where a new incoming non-VoIP emergency call is added to the watchdog.
+     * In this case, the ConnectionService doesn't respond promptly and the timeout will fire.
+     */
+    @Test
+    public void testAddNonVoipEmergencyRingingCallTimeoutWithoutConnection() {
+        setupCallHelper(CallState.RINGING, false, null, false, true);
+
+        // Newly created call which hasn't been added; should schedule timeout.
+        assertEquals(1, mTestScheduledExecutorService.getNumberOfScheduledRunnables());
+        assertTrue(mTestScheduledExecutorService.
+                isRunnableScheduledAtTime(TEST_NON_VOIP_EMERGENCY_TRANSITORY_MILLIS));
+        assertEquals(1, mCallAnomalyWatchdog.getNumberOfScheduledTimeouts());
+
+        // Move the clock to fire the timeout.
+        when(mMockClockProxy.elapsedRealtime()).
+                thenReturn(TEST_NON_VOIP_EMERGENCY_TRANSITORY_MILLIS + 1);
+        mTestScheduledExecutorService.advanceTime(TEST_NON_VOIP_EMERGENCY_TRANSITORY_MILLIS + 1);
+
+        // No timeouts should be pending at this point since the timeout fired.
+        assertEquals(0, mTestScheduledExecutorService.getNumberOfScheduledRunnables());
+        assertEquals(0, mCallAnomalyWatchdog.getNumberOfScheduledTimeouts());
+    }
+
+    /**
+     * Emulate the case where a new incoming VoIP call is added to the watchdog.
+     * In this case, the timeout will fire in intermediate state.
+     */
+    @Test
+    public void testAddVoipRingingCallTimeoutWithConnection() {
+        setupCallHelper(CallState.RINGING, true, mMockConnectionService, true, false);
+
+        // Newly created call which hasn't been added; should schedule timeout.
+        assertEquals(1, mTestScheduledExecutorService.getNumberOfScheduledRunnables());
+        assertTrue(mTestScheduledExecutorService.
+                isRunnableScheduledAtTime(TEST_VOIP_INTERMEDIATE_MILLIS));
+        assertEquals(1, mCallAnomalyWatchdog.getNumberOfScheduledTimeouts());
+
+        // Move the clock to fire the timeout.
+        when(mMockClockProxy.elapsedRealtime()).thenReturn(TEST_VOIP_INTERMEDIATE_MILLIS + 1);
+        mTestScheduledExecutorService.advanceTime(TEST_VOIP_INTERMEDIATE_MILLIS + 1);
+
+        // No timeouts should be pending at this point since the timeout fired.
+        assertEquals(0, mTestScheduledExecutorService.getNumberOfScheduledRunnables());
+        assertEquals(0, mCallAnomalyWatchdog.getNumberOfScheduledTimeouts());
+    }
+
+    /**
+     * Emulate the case where a new incoming VoIP emergency call is added to the watchdog.
+     * In this case, the timeout will fire in intermediate state.
+     */
+    @Test
+    public void testAddVoipEmergencyRingingCallTimeoutWithConnection() {
+        setupCallHelper(CallState.RINGING, true, mMockConnectionService, true, true);
+
+        // Newly created call which hasn't been added; should schedule timeout.
+        assertEquals(1, mTestScheduledExecutorService.getNumberOfScheduledRunnables());
+        assertTrue(mTestScheduledExecutorService.
+                isRunnableScheduledAtTime(TEST_VOIP_EMERGENCY_INTERMEDIATE_MILLIS));
+        assertEquals(1, mCallAnomalyWatchdog.getNumberOfScheduledTimeouts());
+
+        // Move the clock to fire the timeout.
+        when(mMockClockProxy.elapsedRealtime()).
+                thenReturn(TEST_VOIP_EMERGENCY_INTERMEDIATE_MILLIS + 1);
+        mTestScheduledExecutorService.advanceTime(TEST_VOIP_EMERGENCY_INTERMEDIATE_MILLIS + 1);
+
+        // No timeouts should be pending at this point since the timeout fired.
+        assertEquals(0, mTestScheduledExecutorService.getNumberOfScheduledRunnables());
+        assertEquals(0, mCallAnomalyWatchdog.getNumberOfScheduledTimeouts());
+    }
+
+    /**
+     * Emulate the case where a new incoming non-VoIP call is added to the watchdog.
+     * In this case, the timeout will fire in intermediate state.
+     */
+    @Test
+    public void testAddNonVoipRingingCallTimeoutWithConnection() {
+        setupCallHelper(CallState.RINGING, true, mMockConnectionService, false, false);
+
+        // Newly created call which hasn't been added; should schedule timeout.
+        assertEquals(1, mTestScheduledExecutorService.getNumberOfScheduledRunnables());
+        assertTrue(mTestScheduledExecutorService.
+                isRunnableScheduledAtTime(TEST_NON_VOIP_INTERMEDIATE_MILLIS));
+        assertEquals(1, mCallAnomalyWatchdog.getNumberOfScheduledTimeouts());
+
+        // Move the clock to fire the timeout.
+        when(mMockClockProxy.elapsedRealtime()).thenReturn(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);
+        mTestScheduledExecutorService.advanceTime(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);
+
+        // No timeouts should be pending at this point since the timeout fired.
+        assertEquals(0, mTestScheduledExecutorService.getNumberOfScheduledRunnables());
+        assertEquals(0, mCallAnomalyWatchdog.getNumberOfScheduledTimeouts());
+    }
+
+    /**
+     * Emulate the case where a new incoming non-VoIP emergency call is added to the watchdog.
+     * In this case, the timeout will fire in intermediate state.
+     */
+    @Test
+    public void testAddNonVoipEmergencyRingingCallTimeoutWithConnection() {
+        setupCallHelper(CallState.RINGING, true, mMockConnectionService, false, true);
+
+        // Newly created call which hasn't been added; should schedule timeout.
+        assertEquals(1, mTestScheduledExecutorService.getNumberOfScheduledRunnables());
+        assertTrue(mTestScheduledExecutorService.
+                isRunnableScheduledAtTime(TEST_NON_VOIP_EMERGENCY_INTERMEDIATE_MILLIS));
+        assertEquals(1, mCallAnomalyWatchdog.getNumberOfScheduledTimeouts());
+
+        // Move the clock to fire the timeout.
+        when(mMockClockProxy.elapsedRealtime()).
+                thenReturn(TEST_NON_VOIP_EMERGENCY_INTERMEDIATE_MILLIS + 1);
+        mTestScheduledExecutorService.advanceTime(TEST_NON_VOIP_EMERGENCY_INTERMEDIATE_MILLIS + 1);
+
+        // No timeouts should be pending at this point since the timeout fired.
+        assertEquals(0, mTestScheduledExecutorService.getNumberOfScheduledRunnables());
+        assertEquals(0, mCallAnomalyWatchdog.getNumberOfScheduledTimeouts());
+    }
+
+    /**
+     * Emulate the case where a new outgoing VoIP call is added to the watchdog.
+     * In this case, the timeout will fire in transitory state.
+     */
+    @Test
+    public void testVoipPlaceCallTimeout() {
+        // Call will start in connecting state
+        Call call = setupCallHelper(CallState.CONNECTING, false, null, true, false);
+
+        // Assume it is created but the app never sets it to a proper state
+        call.setIsCreateConnectionComplete(false);
+        mCallAnomalyWatchdog.onCallAdded(call);
+
+        // Its transitory, so should schedule timeout.
+        assertEquals(1, mTestScheduledExecutorService.getNumberOfScheduledRunnables());
+        assertTrue(mTestScheduledExecutorService.
+                isRunnableScheduledAtTime(TEST_VOIP_TRANSITORY_MILLIS));
+        assertEquals(1, mCallAnomalyWatchdog.getNumberOfScheduledTimeouts());
+
+        // Move the clock to fire the timeout.
+        when(mMockClockProxy.elapsedRealtime()).thenReturn(TEST_VOIP_TRANSITORY_MILLIS + 1);
+        mTestScheduledExecutorService.advanceTime(TEST_VOIP_TRANSITORY_MILLIS + 1);
+    }
+
+    /**
+     * Emulate the case where a new outgoing VoIP call is added to the watchdog.
+     * In this case, the timeout will fire in transitory state and should report an anomaly.
+     */
+    @Test
+    public void testVoipPlaceCallTimeoutReportAnomaly() {
+        // Call will start in connecting state
+        Call call = setupCallHelper(CallState.CONNECTING, false, null, true, false);
+
+        // Assume it is created but the app never sets it to a proper state
+        call.setIsCreateConnectionComplete(false);
+        mCallAnomalyWatchdog.onCallAdded(call);
+
+        // Move the clock to fire the timeout.
+        when(mMockClockProxy.elapsedRealtime()).thenReturn(TEST_VOIP_TRANSITORY_MILLIS + 1);
+        mTestScheduledExecutorService.advanceTime(TEST_VOIP_TRANSITORY_MILLIS + 1);
+
+        //Ensure an anomaly was reported
+        verify(mAnomalyReporterAdapter).reportAnomaly(
+                CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_UUID,
+                CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_MSG);
+    }
+
+    /**
+     * Emulate the case where a new outgoing VoIP emergency call is added to the watchdog.
+     * In this case, the timeout will fire in transitory state and should report an emergency
+     * anomaly.
+     */
+    @Test
+    public void testVoipEmergencyPlaceCallTimeoutReportAnomaly() {
+        // Call will start in connecting state
+        Call call = setupCallHelper(CallState.CONNECTING, false, null, true, true);
+
+        // Assume it is created but the app never sets it to a proper state
+        call.setIsCreateConnectionComplete(false);
+        mCallAnomalyWatchdog.onCallAdded(call);
+
+        // Move the clock to fire the timeout.
+        when(mMockClockProxy.elapsedRealtime()).
+                thenReturn(TEST_VOIP_EMERGENCY_TRANSITORY_MILLIS + 1);
+        mTestScheduledExecutorService.advanceTime(TEST_VOIP_EMERGENCY_TRANSITORY_MILLIS + 1);
+
+        //Ensure an anomaly was reported
+        verify(mAnomalyReporterAdapter).reportAnomaly(
+                CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_EMERGENCY_CALL_UUID,
+                CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_EMERGENCY_CALL_MSG);
+    }
+
+    /**
+     * Emulate the case where a new outgoing VoIP emergency call is added to the watchdog.
+     * In this case, the timeout will fire in transitory state.
+     */
+    @Test
+    public void testVoipEmergencyPlaceCallTimeout() {
+        // Call will start in connecting state
+        Call call = setupCallHelper(CallState.CONNECTING, false, null, true, true);
+
+        // Assume it is created but the app never sets it to a proper state
+        call.setIsCreateConnectionComplete(false);
+        mCallAnomalyWatchdog.onCallAdded(call);
+
+        // Its transitory, so should schedule timeout.
+        assertEquals(1, mTestScheduledExecutorService.getNumberOfScheduledRunnables());
+        assertTrue(mTestScheduledExecutorService.
+                isRunnableScheduledAtTime(TEST_VOIP_EMERGENCY_TRANSITORY_MILLIS));
+        assertEquals(1, mCallAnomalyWatchdog.getNumberOfScheduledTimeouts());
+
+        // Move the clock to fire the timeout.
+        when(mMockClockProxy.elapsedRealtime()).
+                thenReturn(TEST_VOIP_EMERGENCY_TRANSITORY_MILLIS + 1);
+        mTestScheduledExecutorService.advanceTime(TEST_VOIP_EMERGENCY_TRANSITORY_MILLIS + 1);
+    }
+
+    /**
+     * Emulate the case where a new outgoing non-VoIP call is added to the watchdog.
+     * In this case, the timeout will fire in transitory state.
+     */
+    @Test
+    public void testNonVoipPlaceCallTimeout() {
+        // Call will start in connecting state
+        Call call = setupCallHelper(CallState.CONNECTING, false, null, false, false);
+
+        // Assume it is created but the app never sets it to a proper state
+        call.setIsCreateConnectionComplete(false);
+        mCallAnomalyWatchdog.onCallAdded(call);
+
+        // Its transitory, so should schedule timeout.
+        assertEquals(1, mTestScheduledExecutorService.getNumberOfScheduledRunnables());
+        assertTrue(mTestScheduledExecutorService.
+                isRunnableScheduledAtTime(TEST_NON_VOIP_TRANSITORY_MILLIS));
+        assertEquals(1, mCallAnomalyWatchdog.getNumberOfScheduledTimeouts());
+
+        // Move the clock to fire the timeout.
+        when(mMockClockProxy.elapsedRealtime()).thenReturn(TEST_NON_VOIP_TRANSITORY_MILLIS + 1);
+        mTestScheduledExecutorService.advanceTime(TEST_NON_VOIP_TRANSITORY_MILLIS + 1);
+    }
+
+    /**
+     * Emulate the case where a new outgoing non-VoIP emergency call is added to the watchdog.
+     * In this case, the timeout will fire in transitory state.
+     */
+    @Test
+    public void testNonVoipEmergencyPlaceCallTimeout() {
+        // Call will start in connecting state
+        Call call = setupCallHelper(CallState.CONNECTING, false, null, false, true);
+
+        // Assume it is created but the app never sets it to a proper state
+        call.setIsCreateConnectionComplete(false);
+        mCallAnomalyWatchdog.onCallAdded(call);
+
+        // Its transitory, so should schedule timeout.
+        assertEquals(1, mTestScheduledExecutorService.getNumberOfScheduledRunnables());
+        assertTrue(mTestScheduledExecutorService.
+                isRunnableScheduledAtTime(TEST_NON_VOIP_EMERGENCY_TRANSITORY_MILLIS));
+        assertEquals(1, mCallAnomalyWatchdog.getNumberOfScheduledTimeouts());
+
+        // Move the clock to fire the timeout.
+        when(mMockClockProxy.elapsedRealtime()).
+                thenReturn(TEST_NON_VOIP_EMERGENCY_TRANSITORY_MILLIS + 1);
+        mTestScheduledExecutorService.
+                advanceTime(TEST_NON_VOIP_EMERGENCY_TRANSITORY_MILLIS + 1);
+    }
+
+    /**
+     * Emulate the case where a new incoming call is created but the connection fails for a known
+     * reason before being added to CallsManager. In this case, the watchdog should stop tracking
+     * the call and not trigger an anomaly report.
+     */
+    @Test
+    public void testIncomingCallCreatedButNotAddedNoAnomalyReport() {
+        //The call is created:
+        Call call = getCall();
+        call.setState(CallState.NEW, "foo");
+        call.setIsCreateConnectionComplete(false);
+        mCallAnomalyWatchdog.onStartCreateConnection(call);
+
+        //The connection fails before being added to CallsManager for a known reason:
+        call.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED));
+
+        // Move the clock forward:
+        when(mMockClockProxy.elapsedRealtime()).
+                thenReturn(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);
+        mTestScheduledExecutorService.advanceTime(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);
+
+        //Ensure an anomaly report is not generated:
+        verify(mAnomalyReporterAdapter, never()).reportAnomaly(
+                CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_UUID,
+                CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_MSG);
+    }
+
+    /**
+     * Emulate the case where a new outgoing call is created but the connection fails for a known
+     * reason before being added to CallsManager. In this case, the watchdog should stop tracking
+     * the call and not trigger an anomaly report.
+     */
+    @Test
+    public void testOutgoingCallCreatedButNotAddedNoAnomalyReport() {
+        //The call is created:
+        Call call = getCall();
+        call.setCallDirection(Call.CALL_DIRECTION_OUTGOING);
+        call.setState(CallState.NEW, "foo");
+        call.setIsCreateConnectionComplete(false);
+        mCallAnomalyWatchdog.onStartCreateConnection(call);
+
+        //The connection fails before being added to CallsManager for a known reason.
+        call.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED));
+
+        // Move the clock forward:
+        when(mMockClockProxy.elapsedRealtime()).
+                thenReturn(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);
+        mTestScheduledExecutorService.advanceTime(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);
+
+        //Ensure an anomaly report is not generated:
+        verify(mAnomalyReporterAdapter, never()).reportAnomaly(
+                CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_UUID,
+                CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_MSG);
+    }
+
+    /**
+     * Emulate the case where a new incoming call is created but the connection fails for a known
+     * reason before being added to CallsManager and CallsManager notifies the watchdog by invoking
+     * {@link CallsManager.CallsManagerListener#onCreateConnectionFailed(Call)}.
+     * In this case, the watchdog should stop tracking the call and not trigger an anomaly report.
+     */
+    @Test
+    public void testCallCreatedButNotAddedPreventsAnomalyReport() {
+        //The call is created:
+        Call call = getCall();
+        call.setState(CallState.NEW, "foo");
+        call.setIsCreateConnectionComplete(false);
+        mCallAnomalyWatchdog.onStartCreateConnection(call);
+
+        //Telecom cancels the connection before adding it to CallsManager:
+        mCallAnomalyWatchdog.onCreateConnectionFailed(call);
+
+        // Move the clock forward:
+        when(mMockClockProxy.elapsedRealtime()).
+                thenReturn(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);
+        mTestScheduledExecutorService.advanceTime(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);
+
+        //Ensure an anomaly report is not generated:
+        verify(mAnomalyReporterAdapter, never()).reportAnomaly(
+                CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_UUID,
+                CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_MSG);
+    }
+
+
+    /**
+     * @return an instance of {@link Call} for testing purposes.
+     */
+    private Call getCall() {
+        return new Call(
+                "1", /* callId */
+                mContext,
+                mMockCallsManager,
+                mLock,
+                null /* ConnectionServiceRepository */,
+                mMockPhoneNumberUtilsAdapter,
+                Uri.parse("tel:6505551212"),
+                null /* GatewayInfo */,
+                null /* connectionManagerPhoneAccountHandle */,
+                SIM_1_HANDLE,
+                Call.CALL_DIRECTION_INCOMING,
+                false /* shouldAttachToExistingConnection*/,
+                false /* isConference */,
+                mMockClockProxy,
+                mMockToastProxy);
+    }
+}
\ No newline at end of file
diff --git a/tests/src/com/android/server/telecom/tests/CallAttributesTests.java b/tests/src/com/android/server/telecom/tests/CallAttributesTests.java
new file mode 100644
index 0000000..acb913e
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/CallAttributesTests.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2022 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.server.telecom.tests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.isA;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.os.Parcel;
+import android.telecom.CallAttributes;
+import android.telecom.PhoneAccountHandle;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class CallAttributesTests extends TelecomTestCase {
+
+    private static final PhoneAccountHandle mHandle = new PhoneAccountHandle(
+            new ComponentName("foo", "bar"), "1");
+    private static final String TEST_NAME = "Larry Page";
+    private static final Uri TEST_URI = Uri.fromParts("tel", "abc", "123");
+    @Mock private Parcel mParcel;
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Override
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Test
+    public void testRequiredAttributes() {
+        CallAttributes callAttributes = new CallAttributes.Builder(mHandle,
+                CallAttributes.DIRECTION_OUTGOING, TEST_NAME, TEST_URI).build();
+
+        assertEquals(CallAttributes.DIRECTION_OUTGOING, callAttributes.getDirection());
+        assertEquals(mHandle, callAttributes.getPhoneAccountHandle());
+    }
+
+    @Test
+    public void testInvalidDirectionAttributes() {
+        assertThrows(IllegalArgumentException.class, () ->
+                new CallAttributes.Builder(mHandle, -1, TEST_NAME, TEST_URI).build()
+        );
+    }
+
+    @Test
+    public void testInvalidCallType() {
+        assertThrows(IllegalArgumentException.class, () ->
+                new CallAttributes.Builder(mHandle, CallAttributes.DIRECTION_OUTGOING,
+                        TEST_NAME, TEST_URI).setCallType(-1).build()
+        );
+    }
+
+    @Test
+    public void testOptionalAttributes() {
+        CallAttributes callAttributes = new CallAttributes.Builder(mHandle,
+                CallAttributes.DIRECTION_OUTGOING, TEST_NAME, TEST_URI)
+                .setCallCapabilities(CallAttributes.SUPPORTS_SET_INACTIVE)
+                .setCallType(CallAttributes.AUDIO_CALL)
+                .build();
+
+        assertEquals(CallAttributes.DIRECTION_OUTGOING, callAttributes.getDirection());
+        assertEquals(mHandle, callAttributes.getPhoneAccountHandle());
+        assertEquals(CallAttributes.SUPPORTS_SET_INACTIVE, callAttributes.getCallCapabilities());
+        assertEquals(CallAttributes.AUDIO_CALL, callAttributes.getCallType());
+        assertEquals(TEST_URI, callAttributes.getAddress());
+        assertEquals(TEST_NAME, callAttributes.getDisplayName());
+    }
+
+    @Test
+    public void testDescribeContents() {
+        CallAttributes callAttributes = new CallAttributes.Builder(mHandle,
+                CallAttributes.DIRECTION_OUTGOING, TEST_NAME, TEST_URI).build();
+
+        assertEquals(0, callAttributes.describeContents());
+    }
+
+    @Test
+    public void testWriteToParcel() {
+        // GIVEN
+        CallAttributes callAttributes = new CallAttributes.Builder(mHandle,
+                CallAttributes.DIRECTION_OUTGOING, TEST_NAME, TEST_URI)
+                .setCallCapabilities(CallAttributes.SUPPORTS_SET_INACTIVE)
+                .setCallType(CallAttributes.AUDIO_CALL)
+                .build();
+
+        // WHEN
+        callAttributes.writeToParcel(mParcel, 0);
+
+        // THEN
+        verify(mParcel, times(1))
+                .writeParcelable(isA(PhoneAccountHandle.class), isA(Integer.class));
+        verify(mParcel, times(1)).writeCharSequence(isA(CharSequence.class));
+        verify(mParcel, times(1))
+                .writeParcelable(isA(Uri.class), isA(Integer.class));
+        verify(mParcel, times(3)).writeInt(isA(Integer.class));
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java b/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
index 4adafc8..3d06ad0 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
@@ -51,6 +51,7 @@
 import java.util.stream.Collectors;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.nullable;
@@ -677,6 +678,30 @@
         assertTrue(mCallAudioManager.isCallVoip(child));
     }
 
+    @SmallTest
+    @Test
+    public void testOnCallStreamingStateChanged() {
+        Call call = mock(Call.class);
+        ArgumentCaptor<CallAudioModeStateMachine.MessageArgs> captor = makeNewCaptor();
+
+        // non-streaming call
+        mCallAudioManager.onCallStreamingStateChanged(call, false /* isStreamingCall */);
+        verify(mCallAudioModeStateMachine, never()).sendMessageWithArgs(anyInt(),
+                any(MessageArgs.class));
+
+        // start streaming
+        mCallAudioManager.onCallStreamingStateChanged(call, true /* isStreamingCall */);
+        verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+                eq(CallAudioModeStateMachine.START_CALL_STREAMING), captor.capture());
+        assertTrue(captor.getValue().isStreaming);
+
+        // stop streaming
+        mCallAudioManager.onCallStreamingStateChanged(call, false /* isStreamingCall */);
+        verify(mCallAudioModeStateMachine).sendMessageWithArgs(
+                eq(CallAudioModeStateMachine.STOP_CALL_STREAMING), captor.capture());
+        assertFalse(captor.getValue().isStreaming);
+    }
+
     private Call createSimulatedRingingCall() {
         Call call = mock(Call.class);
         when(call.getState()).thenReturn(CallState.SIMULATED_RINGING);
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
index 38f58fd..0f38ca5 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioModeStateMachineTest.java
@@ -34,6 +34,7 @@
 import org.mockito.Mock;
 
 import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Mockito.clearInvocations;
@@ -49,6 +50,7 @@
     @Mock private SystemStateHelper mSystemStateHelper;
     @Mock private AudioManager mAudioManager;
     @Mock private CallAudioManager mCallAudioManager;
+    @Mock private CallAudioRouteStateMachine mCallAudioRouteStateMachine;
 
     private HandlerThread mTestThread;
 
@@ -58,6 +60,8 @@
         mTestThread = new HandlerThread("CallAudioModeStateMachineTest");
         mTestThread.start();
         super.setUp();
+        when(mCallAudioManager.getCallAudioRouteStateMachine())
+                .thenReturn(mCallAudioRouteStateMachine);
     }
 
     @Override
@@ -102,6 +106,64 @@
 
     @SmallTest
     @Test
+    public void testSwitchToStreamingMode() {
+        CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
+                mAudioManager, mTestThread.getLooper());
+        sm.setCallAudioManager(mCallAudioManager);
+        sm.sendMessage(CallAudioModeStateMachine.ABANDON_FOCUS_FOR_TESTING);
+        waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+
+        resetMocks();
+        when(mCallAudioManager.startRinging()).thenReturn(false);
+
+        sm.sendMessage(CallAudioModeStateMachine.START_CALL_STREAMING, new Builder()
+                .setHasActiveOrDialingCalls(false)
+                .setHasRingingCalls(false)
+                .setHasHoldingCalls(false)
+                .setIsTonePlaying(false)
+                .setHasActiveOrDialingCalls(true)
+                .setForegroundCallIsVoip(true)
+                .setIsStreaming(true)
+                .setSession(null)
+                .build());
+        waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+
+        assertEquals(CallAudioModeStateMachine.STREAMING_STATE_NAME, sm.getCurrentStateName());
+
+        verify(mAudioManager, never()).requestAudioFocusForCall(anyInt(), anyInt());
+        verify(mAudioManager).setMode(eq(AudioManager.MODE_CALL_REDIRECT));
+    }
+
+    @SmallTest
+    @Test
+    public void testExitStreamingMode() {
+        CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
+                mAudioManager, mTestThread.getLooper());
+        sm.setCallAudioManager(mCallAudioManager);
+        sm.sendMessage(CallAudioModeStateMachine.ENTER_STREAMING_FOCUS_FOR_TESTING);
+        waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+
+        resetMocks();
+        when(mCallAudioManager.startRinging()).thenReturn(false);
+
+        sm.sendMessage(CallAudioModeStateMachine.STOP_CALL_STREAMING, new Builder()
+                .setHasActiveOrDialingCalls(false)
+                .setHasRingingCalls(false)
+                .setHasHoldingCalls(false)
+                .setIsTonePlaying(false)
+                .setForegroundCallIsVoip(true)
+                .setIsStreaming(false)
+                .setSession(null)
+                .build());
+        waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+
+        assertEquals(CallAudioModeStateMachine.UNFOCUSED_STATE_NAME, sm.getCurrentStateName());
+
+        verify(mAudioManager, never()).requestAudioFocusForCall(anyInt(), anyInt());
+    }
+
+    @SmallTest
+    @Test
     public void testNoRingWhenDeviceIsAtEar() {
         CallAudioModeStateMachine sm = new CallAudioModeStateMachine(mSystemStateHelper,
                 mAudioManager, mTestThread.getLooper());
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRoutePeripheralAdapterTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRoutePeripheralAdapterTest.java
new file mode 100644
index 0000000..dfe1483
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRoutePeripheralAdapterTest.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2022 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.server.telecom.tests;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.telecom.CallAudioRoutePeripheralAdapter;
+import com.android.server.telecom.CallAudioRouteStateMachine;
+import com.android.server.telecom.DockManager;
+import com.android.server.telecom.WiredHeadsetManager;
+import com.android.server.telecom.bluetooth.BluetoothRouteManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+
+@RunWith(JUnit4.class)
+public class CallAudioRoutePeripheralAdapterTest extends TelecomTestCase {
+    CallAudioRoutePeripheralAdapter mAdapter;
+
+    @Mock private CallAudioRouteStateMachine mCallAudioRouteStateMachine;
+    @Mock private BluetoothRouteManager mBluetoothRouteManager;
+    @Mock private WiredHeadsetManager mWiredHeadsetManager;
+    @Mock private DockManager mDockManager;
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mAdapter = new CallAudioRoutePeripheralAdapter(
+                mCallAudioRouteStateMachine,
+                mBluetoothRouteManager,
+                mWiredHeadsetManager,
+                mDockManager);
+    }
+
+    @Override
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @SmallTest
+    @Test
+    public void testIsBluetoothAudioOn() {
+        when(mBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
+        assertFalse(mAdapter.isBluetoothAudioOn());
+
+        when(mBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(true);
+        assertTrue(mAdapter.isBluetoothAudioOn());
+    }
+
+    @SmallTest
+    @Test
+    public void testIsHearingAidDeviceOn() {
+        when(mBluetoothRouteManager.isCachedHearingAidDevice(any())).thenReturn(false);
+        assertFalse(mAdapter.isHearingAidDeviceOn());
+
+        when(mBluetoothRouteManager.isCachedHearingAidDevice(any())).thenReturn(true);
+        assertTrue(mAdapter.isHearingAidDeviceOn());
+    }
+
+    @SmallTest
+    @Test
+    public void testIsLeAudioDeviceOn() {
+        when(mBluetoothRouteManager.isCachedLeAudioDevice(any())).thenReturn(false);
+        assertFalse(mAdapter.isLeAudioDeviceOn());
+
+        when(mBluetoothRouteManager.isCachedLeAudioDevice(any())).thenReturn(true);
+        assertTrue(mAdapter.isLeAudioDeviceOn());
+    }
+
+    @SmallTest
+    @Test
+    public void testOnBluetoothDeviceListChanged() {
+        mAdapter.onBluetoothDeviceListChanged();
+        verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo(
+                CallAudioRouteStateMachine.BLUETOOTH_DEVICE_LIST_CHANGED);
+    }
+
+    @SmallTest
+    @Test
+    public void testOnBluetoothActiveDevicePresent() {
+        mAdapter.onBluetoothActiveDevicePresent();
+        verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo(
+                CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_PRESENT);
+    }
+
+    @SmallTest
+    @Test
+    public void testOnBluetoothActiveDeviceGone() {
+        mAdapter.onBluetoothActiveDeviceGone();
+        verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo(
+                CallAudioRouteStateMachine.BT_ACTIVE_DEVICE_GONE);
+    }
+
+    @SmallTest
+    @Test
+    public void testOnBluetoothAudioConnected() {
+        mAdapter.onBluetoothAudioConnected();
+        verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo(
+                CallAudioRouteStateMachine.BT_AUDIO_CONNECTED);
+    }
+
+    @SmallTest
+    @Test
+    public void testOnBluetoothAudioDisconnected() {
+        mAdapter.onBluetoothAudioDisconnected();
+        verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo(
+                CallAudioRouteStateMachine.BT_AUDIO_DISCONNECTED);
+    }
+
+    @SmallTest
+    @Test
+    public void testOnUnexpectedBluetoothStateChange() {
+        mAdapter.onUnexpectedBluetoothStateChange();
+        verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo(
+                CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
+    }
+
+    @SmallTest
+    @Test
+    public void testOnWiredHeadsetPluggedInChangedNoChange() {
+        mAdapter.onWiredHeadsetPluggedInChanged(false, false);
+        mAdapter.onWiredHeadsetPluggedInChanged(true, true);
+        verify(mCallAudioRouteStateMachine, never()).sendMessageWithSessionInfo(anyInt());
+    }
+
+    @SmallTest
+    @Test
+    public void testOnWiredHeadsetPluggedInChangedPlugged() {
+        mAdapter.onWiredHeadsetPluggedInChanged(false, true);
+        verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo(
+                CallAudioRouteStateMachine.CONNECT_WIRED_HEADSET);
+    }
+
+    @SmallTest
+    @Test
+    public void testOnWiredHeadsetPluggedInChangedUnplugged() {
+        mAdapter.onWiredHeadsetPluggedInChanged(true, false);
+        verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo(
+                CallAudioRouteStateMachine.DISCONNECT_WIRED_HEADSET);
+    }
+
+    @SmallTest
+    @Test
+    public void testOnDockChangedConnected() {
+        mAdapter.onDockChanged(true);
+        verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo(
+                CallAudioRouteStateMachine.CONNECT_DOCK);
+    }
+
+    @SmallTest
+    @Test
+    public void testOnDockChangedDisconnected() {
+        mAdapter.onDockChanged(false);
+        verify(mCallAudioRouteStateMachine).sendMessageWithSessionInfo(
+                CallAudioRouteStateMachine.DISCONNECT_DOCK);
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
index 6092293..569c487 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
@@ -53,7 +53,9 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -89,6 +91,7 @@
     @Mock ConnectionServiceWrapper mockConnectionServiceWrapper;
     @Mock WiredHeadsetManager mockWiredHeadsetManager;
     @Mock StatusBarNotifier mockStatusBarNotifier;
+    @Mock Call fakeSelfManagedCall;
     @Mock Call fakeCall;
     @Mock CallAudioManager mockCallAudioManager;
 
@@ -116,11 +119,16 @@
         };
 
         when(mockCallsManager.getForegroundCall()).thenReturn(fakeCall);
+        when(mockCallsManager.getTrackedCalls()).thenReturn(Set.of(fakeCall));
         when(mockCallsManager.getLock()).thenReturn(mLock);
         when(mockCallsManager.hasVideoCall()).thenReturn(false);
         when(fakeCall.getConnectionService()).thenReturn(mockConnectionServiceWrapper);
         when(fakeCall.isAlive()).thenReturn(true);
         when(fakeCall.getSupportedAudioRoutes()).thenReturn(CallAudioState.ROUTE_ALL);
+        when(fakeSelfManagedCall.getConnectionService()).thenReturn(mockConnectionServiceWrapper);
+        when(fakeSelfManagedCall.isAlive()).thenReturn(true);
+        when(fakeSelfManagedCall.getSupportedAudioRoutes()).thenReturn(CallAudioState.ROUTE_ALL);
+        when(fakeSelfManagedCall.isSelfManaged()).thenReturn(true);
 
         doNothing().when(mockConnectionServiceWrapper).onCallAudioStateChanged(any(Call.class),
                 any(CallAudioState.class));
@@ -145,7 +153,8 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */);
 
         // Since we don't know if we're on a platform with an earpiece or not, all we can do
         // is ensure the stateMachine construction didn't fail.  But at least we exercised the
@@ -153,6 +162,52 @@
         assertNotNull(stateMachine);
     }
 
+    @SmallTest
+    @Test
+    public void testTrackedCallsReceiveAudioRouteChange() {
+        CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+                mContext,
+                mockCallsManager,
+                mockBluetoothRouteManager,
+                mockWiredHeadsetManager,
+                mockStatusBarNotifier,
+                mAudioServiceFactory,
+                CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT,
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */);
+        stateMachine.setCallAudioManager(mockCallAudioManager);
+
+        Set<Call> trackedCalls = new HashSet<>(Arrays.asList(fakeCall, fakeSelfManagedCall));
+        when(mockCallsManager.getTrackedCalls()).thenReturn(trackedCalls);
+
+        // start state --> ROUTE_EARPIECE
+        CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER);
+        stateMachine.initialize(initState);
+
+        stateMachine.setCallAudioManager(mockCallAudioManager);
+
+        assertEquals(stateMachine.getCurrentCallAudioState().getRoute(),
+                CallAudioRouteStateMachine.ROUTE_EARPIECE);
+
+        // ROUTE_EARPIECE  --> ROUTE_SPEAKER
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_SPEAKER,
+                CallAudioRouteStateMachine.SPEAKER_ON);
+
+        stateMachine.sendMessageWithSessionInfo(
+                CallAudioRouteStateMachine.UPDATE_SYSTEM_AUDIO_ROUTE);
+
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+
+        // assert expected end state
+        assertEquals(stateMachine.getCurrentCallAudioState().getRoute(),
+                CallAudioRouteStateMachine.ROUTE_SPEAKER);
+        // should update the audio route on all tracked calls ...
+        verify(mockConnectionServiceWrapper, times(trackedCalls.size()))
+                .onCallAudioStateChanged(any(), any());
+    }
+
     @MediumTest
     @Test
     public void testStreamRingMuteChange() {
@@ -164,7 +219,8 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */);
         stateMachine.setCallAudioManager(mockCallAudioManager);
         CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER);
@@ -207,7 +263,8 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */);
 
         when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
         when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true);
@@ -252,7 +309,8 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */);
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
@@ -296,7 +354,8 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */);
         stateMachine.setCallAudioManager(mockCallAudioManager);
         Collection<BluetoothDevice> availableDevices = Collections.singleton(bluetoothDevice1);
 
@@ -374,7 +433,8 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */);
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
@@ -410,7 +470,8 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */);
         stateMachine.setCallAudioManager(mockCallAudioManager);
         setInBandRing(false);
         when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
@@ -465,7 +526,8 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */);
         stateMachine.setCallAudioManager(mockCallAudioManager);
         List<BluetoothDevice> availableDevices =
                 Arrays.asList(bluetoothDevice1, bluetoothDevice2, bluetoothDevice3);
@@ -515,7 +577,8 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */);
         stateMachine.setCallAudioManager(mockCallAudioManager);
         when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false);
         CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
@@ -546,7 +609,8 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */);
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false);
@@ -580,7 +644,8 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */);
         stateMachine.setCallAudioManager(mockCallAudioManager);
         List<BluetoothDevice> availableDevices =
                 Arrays.asList(bluetoothDevice1, bluetoothDevice2);
@@ -695,11 +760,44 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */);
         stateMachine.initialize();
         assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
     }
 
+    @SmallTest
+    @Test
+    public void testStreamingRoute() {
+        CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+                mContext,
+                mockCallsManager,
+                mockBluetoothRouteManager,
+                mockWiredHeadsetManager,
+                mockStatusBarNotifier,
+                mAudioServiceFactory,
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */);
+        stateMachine.setCallAudioManager(mockCallAudioManager);
+
+        CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
+            CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER);
+        stateMachine.initialize(initState);
+
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.STREAMING_FORCE_ENABLED);
+        CallAudioState expectedEndState = new CallAudioState(false,
+                CallAudioState.ROUTE_STREAMING, CallAudioState.ROUTE_STREAMING);
+
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        verifyNewSystemCallAudioState(initState, expectedEndState);
+        resetMocks();
+        stateMachine.sendMessageWithSessionInfo(
+                CallAudioRouteStateMachine.STREAMING_FORCE_DISABLED);
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+        assertEquals(initState, stateMachine.getCurrentCallAudioState());
+    }
+
     private void initializationTestHelper(CallAudioState expectedState,
             int earpieceControl) {
         when(mockWiredHeadsetManager.isPluggedIn()).thenReturn(
@@ -717,7 +815,8 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 earpieceControl,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */);
         stateMachine.initialize();
         assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
     }
@@ -749,6 +848,7 @@
                 mockConnectionServiceWrapper);
         fakeCall = mock(Call.class);
         when(mockCallsManager.getForegroundCall()).thenReturn(fakeCall);
+        when(mockCallsManager.getTrackedCalls()).thenReturn(Set.of(fakeCall));
         when(fakeCall.getConnectionService()).thenReturn(mockConnectionServiceWrapper);
         when(fakeCall.isAlive()).thenReturn(true);
         when(fakeCall.getSupportedAudioRoutes()).thenReturn(CallAudioState.ROUTE_ALL);
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
index 3eacc3a..cf684de 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
@@ -64,6 +64,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Set;
 
 @RunWith(Parameterized.class)
 public class CallAudioRouteTransitionTests extends TelecomTestCase {
@@ -182,6 +183,7 @@
         };
 
         when(mockCallsManager.getForegroundCall()).thenReturn(fakeCall);
+        when(mockCallsManager.getTrackedCalls()).thenReturn(Set.of(fakeCall));
         when(mockCallsManager.getLock()).thenReturn(mLock);
         when(mockCallsManager.hasVideoCall()).thenReturn(false);
         when(fakeCall.getConnectionService()).thenReturn(mockConnectionServiceWrapper);
@@ -267,7 +269,8 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 mParams.earpieceControl,
-                mHandlerThread.getLooper());
+                mHandlerThread.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */);
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         setupMocksForParams(stateMachine, mParams);
@@ -363,7 +366,8 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 mParams.earpieceControl,
-                mHandlerThread.getLooper());
+                mHandlerThread.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */);
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         // Set up bluetooth and speakerphone state
diff --git a/tests/src/com/android/server/telecom/tests/CallControlTest.java b/tests/src/com/android/server/telecom/tests/CallControlTest.java
new file mode 100644
index 0000000..2613206
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/CallControlTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2022 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.server.telecom.tests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.ComponentName;
+import android.os.OutcomeReceiver;
+import android.telecom.CallControl;
+import android.telecom.CallException;
+import android.telecom.PhoneAccountHandle;
+
+import com.android.internal.telecom.ClientTransactionalServiceRepository;
+import com.android.internal.telecom.ICallControl;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.UUID;
+
+public class CallControlTest extends TelecomTestCase {
+
+    private static final PhoneAccountHandle mHandle = new PhoneAccountHandle(
+            new ComponentName("foo", "bar"), "1");
+
+    @Mock
+    private ICallControl mICallControl;
+    @Mock
+    private ClientTransactionalServiceRepository mRepository;
+    private static final String CALL_ID_1 = UUID.randomUUID().toString();
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Override
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Test
+    public void testGetCallId() {
+        CallControl control = new CallControl(CALL_ID_1, mICallControl, mRepository, mHandle);
+        assertEquals(CALL_ID_1, control.getCallId().toString());
+    }
+
+    @Test
+    public void testCallControlHitsIllegalStateException() {
+        CallControl control = new CallControl(CALL_ID_1, null, mRepository, mHandle);
+        assertThrows(IllegalStateException.class, () ->
+                control.setInactive(Runnable::run, result -> {
+                }));
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/CallEndpointControllerTest.java b/tests/src/com/android/server/telecom/tests/CallEndpointControllerTest.java
new file mode 100644
index 0000000..f4008aa
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/CallEndpointControllerTest.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright 2022, 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.server.telecom.tests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothDevice;
+import android.os.ResultReceiver;
+import android.telecom.CallAudioState;
+import android.telecom.CallEndpoint;
+import android.test.mock.MockContext;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallAudioManager;
+import com.android.server.telecom.CallEndpointController;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.ConnectionServiceWrapper;
+
+import org.junit.Before;
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+@RunWith(JUnit4.class)
+public class CallEndpointControllerTest extends TelecomTestCase {
+    private static final BluetoothDevice bluetoothDevice1 =
+            BluetoothRouteManagerTest.makeBluetoothDevice("00:00:00:00:00:01");
+    private static final BluetoothDevice bluetoothDevice2 =
+            BluetoothRouteManagerTest.makeBluetoothDevice("00:00:00:00:00:02");
+    private static final Collection<BluetoothDevice> availableBluetooth1 =
+            Arrays.asList(bluetoothDevice1, bluetoothDevice2);
+    private static final Collection<BluetoothDevice> availableBluetooth2 =
+            Arrays.asList(bluetoothDevice1);
+
+    private static final CallAudioState audioState1 = new CallAudioState(false,
+            CallAudioState.ROUTE_EARPIECE, CallAudioState.ROUTE_ALL, null, availableBluetooth1);
+    private static final CallAudioState audioState2 = new CallAudioState(false,
+            CallAudioState.ROUTE_BLUETOOTH, CallAudioState.ROUTE_ALL, bluetoothDevice1,
+            availableBluetooth1);
+    private static final CallAudioState audioState3 = new CallAudioState(false,
+            CallAudioState.ROUTE_BLUETOOTH, CallAudioState.ROUTE_ALL, bluetoothDevice2,
+            availableBluetooth1);
+    private static final CallAudioState audioState4 = new CallAudioState(false,
+            CallAudioState.ROUTE_BLUETOOTH, CallAudioState.ROUTE_ALL, bluetoothDevice1,
+            availableBluetooth2);
+    private static final CallAudioState audioState5 = new CallAudioState(true,
+            CallAudioState.ROUTE_EARPIECE, CallAudioState.ROUTE_ALL, null, availableBluetooth1);
+    private static final CallAudioState audioState6 = new CallAudioState(false,
+            CallAudioState.ROUTE_EARPIECE, CallAudioState.ROUTE_EARPIECE, null,
+            availableBluetooth1);
+    private static final CallAudioState audioState7 = new CallAudioState(false,
+            CallAudioState.ROUTE_STREAMING, CallAudioState.ROUTE_ALL, null, availableBluetooth1);
+
+    private CallEndpointController mCallEndpointController;
+
+    @Mock private CallsManager mCallsManager;
+    @Mock private Call mCall;
+    @Mock private ConnectionServiceWrapper mConnectionService;
+    @Mock private CallAudioManager mCallAudioManager;
+    @Mock private MockContext mMockContext;
+    @Mock private ResultReceiver mResultReceiver;
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        mCallEndpointController = new CallEndpointController(mMockContext, mCallsManager);
+        doReturn(new HashSet<>(Arrays.asList(mCall))).when(mCallsManager).getTrackedCalls();
+        doReturn(mConnectionService).when(mCall).getConnectionService();
+        doReturn(mCallAudioManager).when(mCallsManager).getCallAudioManager();
+        when(mMockContext.getText(R.string.callendpoint_name_earpiece)).thenReturn("Earpiece");
+        when(mMockContext.getText(R.string.callendpoint_name_bluetooth)).thenReturn("Bluetooth");
+        when(mMockContext.getText(R.string.callendpoint_name_wiredheadset))
+                .thenReturn("Wired headset");
+        when(mMockContext.getText(R.string.callendpoint_name_speaker)).thenReturn("Speaker");
+        when(mMockContext.getText(R.string.callendpoint_name_streaming)).thenReturn("External");
+        when(mMockContext.getText(R.string.callendpoint_name_unknown)).thenReturn("Unknown");
+    }
+
+    @Override
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Test
+    public void testCurrentEndpointChangedToBluetooth() throws Exception {
+        mCallEndpointController.onCallAudioStateChanged(audioState1, audioState2);
+        CallEndpoint endpoint = mCallEndpointController.getCurrentCallEndpoint();
+        Set<CallEndpoint> availableEndpoints = mCallEndpointController.getAvailableEndpoints();
+        String bluetoothAddress = mCallEndpointController.getBluetoothAddress(endpoint);
+
+        // Earpiece, Wired headset, Speaker and two Bluetooth endpoint is available
+        assertEquals(5, availableEndpoints.size());
+        // type of current CallEndpoint is Bluetooth
+        assertEquals(CallEndpoint.TYPE_BLUETOOTH, endpoint.getEndpointType());
+        assertEquals(bluetoothDevice1.getAddress(), bluetoothAddress);
+
+        verify(mCallsManager).updateCallEndpoint(eq(endpoint));
+        verify(mConnectionService, times(1)).onCallEndpointChanged(eq(mCall), eq(endpoint));
+        verify(mCallsManager, never()).updateAvailableCallEndpoints(any());
+        verify(mConnectionService, never()).onAvailableCallEndpointsChanged(any(), any());
+        verify(mCallsManager, never()).updateMuteState(anyBoolean());
+        verify(mConnectionService, never()).onMuteStateChanged(any(), anyBoolean());
+    }
+
+    @Test
+    public void testCurrentEndpointChangedToStreaming() throws Exception {
+        mCallEndpointController.onCallAudioStateChanged(audioState1, audioState7);
+        CallEndpoint endpoint = mCallEndpointController.getCurrentCallEndpoint();
+        Set<CallEndpoint> availableEndpoints = mCallEndpointController.getAvailableEndpoints();
+
+        // Only Streaming is available, but it will not be reported via the available endpoints list
+        assertEquals(0, availableEndpoints.size());
+        assertNotNull(availableEndpoints);
+        // type of current CallEndpoint is Streaming
+        assertEquals(CallEndpoint.TYPE_STREAMING, endpoint.getEndpointType());
+
+        verify(mCallsManager).updateCallEndpoint(eq(endpoint));
+        verify(mConnectionService, times(1)).onCallEndpointChanged(eq(mCall), eq(endpoint));
+        verify(mCallsManager).updateAvailableCallEndpoints(eq(availableEndpoints));
+        verify(mConnectionService, times(1)).onAvailableCallEndpointsChanged(eq(mCall),
+                eq(availableEndpoints));
+        verify(mCallsManager, never()).updateMuteState(anyBoolean());
+        verify(mConnectionService, never()).onMuteStateChanged(any(), anyBoolean());
+    }
+
+    @Test
+    public void testCurrentEndpointChangedBetweenBluetooth() throws Exception {
+        mCallEndpointController.onCallAudioStateChanged(audioState2, audioState3);
+        CallEndpoint endpoint = mCallEndpointController.getCurrentCallEndpoint();
+        Set<CallEndpoint> availableEndpoints = mCallEndpointController.getAvailableEndpoints();
+        String bluetoothAddress = mCallEndpointController.getBluetoothAddress(endpoint);
+
+        // Earpiece, Wired headset, Speaker and two Bluetooth endpoint is available
+        assertEquals(5, availableEndpoints.size());
+        // type of current CallEndpoint is Bluetooth
+        assertEquals(CallEndpoint.TYPE_BLUETOOTH, endpoint.getEndpointType());
+        assertEquals(bluetoothDevice2.getAddress(), bluetoothAddress);
+
+        verify(mCallsManager).updateCallEndpoint(eq(endpoint));
+        verify(mConnectionService, times(1)).onCallEndpointChanged(eq(mCall), eq(endpoint));
+        verify(mCallsManager, never()).updateAvailableCallEndpoints(any());
+        verify(mConnectionService, never()).onAvailableCallEndpointsChanged(any(), any());
+        verify(mCallsManager, never()).updateMuteState(anyBoolean());
+        verify(mConnectionService, never()).onMuteStateChanged(any(), anyBoolean());
+    }
+
+    @Test
+    public void testAvailableEndpointChanged() throws Exception {
+        mCallEndpointController.onCallAudioStateChanged(audioState1, audioState6);
+        CallEndpoint endpoint = mCallEndpointController.getCurrentCallEndpoint();
+        Set<CallEndpoint> availableEndpoints = mCallEndpointController.getAvailableEndpoints();
+
+        // Only Earpiece is available
+        assertEquals(1, availableEndpoints.size());
+        // type of current CallEndpoint is Earpiece
+        assertEquals(CallEndpoint.TYPE_EARPIECE, endpoint.getEndpointType());
+        assertTrue(availableEndpoints.contains(endpoint));
+
+        verify(mCallsManager, never()).updateCallEndpoint(any());
+        verify(mConnectionService, never()).onCallEndpointChanged(any(), any());
+        verify(mCallsManager).updateAvailableCallEndpoints(eq(availableEndpoints));
+        verify(mConnectionService, times(1)).onAvailableCallEndpointsChanged(eq(mCall),
+                eq(availableEndpoints));
+        verify(mCallsManager, never()).updateMuteState(anyBoolean());
+        verify(mConnectionService, never()).onMuteStateChanged(any(), anyBoolean());
+    }
+
+    @Test
+    public void testAvailableBluetoothEndpointChanged() throws Exception {
+        mCallEndpointController.onCallAudioStateChanged(audioState2, audioState4);
+        CallEndpoint endpoint = mCallEndpointController.getCurrentCallEndpoint();
+        Set<CallEndpoint> availableEndpoints = mCallEndpointController.getAvailableEndpoints();
+        String bluetoothAddress = mCallEndpointController.getBluetoothAddress(endpoint);
+
+        // Earpiece, Wired headset, Speaker and one Bluetooth endpoint is available
+        assertEquals(4, availableEndpoints.size());
+        // type of current CallEndpoint is Bluetooth
+        assertEquals(CallEndpoint.TYPE_BLUETOOTH, endpoint.getEndpointType());
+        assertEquals(bluetoothDevice1.getAddress(), bluetoothAddress);
+
+        verify(mCallsManager, never()).updateCallEndpoint(any());
+        verify(mConnectionService, never()).onCallEndpointChanged(any(), any());
+        verify(mCallsManager).updateAvailableCallEndpoints(eq(availableEndpoints));
+        verify(mConnectionService, times(1)).onAvailableCallEndpointsChanged(eq(mCall),
+                eq(availableEndpoints));
+        verify(mCallsManager, never()).updateMuteState(anyBoolean());
+        verify(mConnectionService, never()).onMuteStateChanged(any(), anyBoolean());
+    }
+
+    @Test
+    public void testMuteStateChanged() throws Exception {
+        mCallEndpointController.onCallAudioStateChanged(audioState1, audioState5);
+        CallEndpoint endpoint = mCallEndpointController.getCurrentCallEndpoint();
+        Set<CallEndpoint> availableEndpoints = mCallEndpointController.getAvailableEndpoints();
+
+        // Earpiece, Wired headset, Speaker and two Bluetooth endpoint is available
+        assertEquals(5, availableEndpoints.size());
+        // type of current CallEndpoint is Earpiece
+        assertEquals(CallEndpoint.TYPE_EARPIECE, endpoint.getEndpointType());
+
+        verify(mCallsManager, never()).updateCallEndpoint(any());
+        verify(mConnectionService, never()).onCallEndpointChanged(any(), any());
+        verify(mCallsManager, never()).updateAvailableCallEndpoints(any());
+        verify(mConnectionService, never()).onAvailableCallEndpointsChanged(any(), any());
+        verify(mCallsManager).updateMuteState(eq(true));
+        verify(mConnectionService, times(1)).onMuteStateChanged(eq(mCall), eq(true));
+    }
+
+    @Test
+    public void testNotifyForcely() throws Exception {
+        mCallEndpointController.onCallAudioStateChanged(audioState1, audioState1);
+        CallEndpoint endpoint = mCallEndpointController.getCurrentCallEndpoint();
+        Set<CallEndpoint> availableEndpoints = mCallEndpointController.getAvailableEndpoints();
+
+        // Earpiece, Wired headset, Speaker and two Bluetooth endpoint is available
+        assertEquals(5, availableEndpoints.size());
+        // type of current CallEndpoint is Earpiece
+        assertEquals(CallEndpoint.TYPE_EARPIECE, endpoint.getEndpointType());
+
+        verify(mCallsManager).updateCallEndpoint(eq(endpoint));
+        verify(mConnectionService, times(1)).onCallEndpointChanged(eq(mCall), eq(endpoint));
+        verify(mCallsManager).updateAvailableCallEndpoints(eq(availableEndpoints));
+        verify(mConnectionService, times(1)).onAvailableCallEndpointsChanged(eq(mCall),
+                eq(availableEndpoints));
+        verify(mCallsManager).updateMuteState(eq(false));
+        verify(mConnectionService, times(1)).onMuteStateChanged(eq(mCall), eq(false));
+    }
+
+    @Test
+    public void testEndpointChangeRequest() throws Exception {
+        mCallEndpointController.onCallAudioStateChanged(null, audioState1);
+        CallEndpoint endpoint1 = mCallEndpointController.getCurrentCallEndpoint();
+
+        mCallEndpointController.onCallAudioStateChanged(audioState1, audioState2);
+        CallEndpoint endpoint2 = mCallEndpointController.getCurrentCallEndpoint();
+
+        mCallEndpointController.requestCallEndpointChange(endpoint1, mResultReceiver);
+        verify(mCallAudioManager).setAudioRoute(eq(CallAudioState.ROUTE_EARPIECE), eq(null));
+
+        mCallEndpointController.requestCallEndpointChange(endpoint2, mResultReceiver);
+        verify(mCallAudioManager).setAudioRoute(eq(CallAudioState.ROUTE_BLUETOOTH),
+                eq(bluetoothDevice1.getAddress()));
+    }
+
+    @Test
+    public void testEndpointChangeRequest_EndpointDoesNotExist() throws Exception {
+        mCallEndpointController.onCallAudioStateChanged(null, audioState2);
+        CallEndpoint endpoint = mCallEndpointController.getCurrentCallEndpoint();
+        mCallEndpointController.onCallAudioStateChanged(audioState2, audioState6);
+
+        mCallEndpointController.requestCallEndpointChange(endpoint, mResultReceiver);
+        verify(mCallAudioManager, never()).setAudioRoute(eq(CallAudioState.ROUTE_BLUETOOTH),
+                eq(bluetoothDevice1.getAddress()));
+        verify(mResultReceiver).send(eq(CallEndpoint.ENDPOINT_OPERATION_FAILED), any());
+    }
+}
\ No newline at end of file
diff --git a/tests/src/com/android/server/telecom/tests/CallExceptionTests.java b/tests/src/com/android/server/telecom/tests/CallExceptionTests.java
new file mode 100644
index 0000000..fa22877
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/CallExceptionTests.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2022 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.server.telecom.tests;
+
+import static android.telecom.CallException.CODE_CALL_CANNOT_BE_SET_TO_ACTIVE;
+import static android.telecom.CallException.CODE_ERROR_UNKNOWN;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.isA;
+import static org.junit.Assert.assertEquals;
+
+import android.os.Parcel;
+import android.telecom.CallException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+public class CallExceptionTests extends TelecomTestCase {
+
+    @Mock private Parcel mParcel;
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Override
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Test
+    public void testExceptionWithCode() {
+        String message = "test message";
+        CallException exception = new CallException(message, CODE_CALL_CANNOT_BE_SET_TO_ACTIVE);
+        assertTrue(exception.getMessage().contains(message));
+        assertEquals(CODE_CALL_CANNOT_BE_SET_TO_ACTIVE, exception.getCode());
+    }
+
+    @Test
+    public void testDescribeContents() {
+        String message = "test message";
+        CallException exception = new CallException(message, CODE_ERROR_UNKNOWN);
+        assertEquals(0, exception.describeContents());
+    }
+
+    @Test
+    public void testWriteToParcel() {
+        // GIVEN
+        String message = "test message";
+        CallException exception = new CallException(message, CODE_ERROR_UNKNOWN);
+
+        // WHEN
+        exception.writeToParcel(mParcel, 0);
+
+        // THEN
+        verify(mParcel, times(1)).writeString8(isA(String.class));
+        verify(mParcel, times(1)).writeInt(isA(Integer.class));
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/CallFailureCauseTest.java b/tests/src/com/android/server/telecom/tests/CallFailureCauseTest.java
new file mode 100644
index 0000000..7c0f649
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/CallFailureCauseTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.telecom.tests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.android.server.telecom.stats.CallFailureCause;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class CallFailureCauseTest extends TelecomTestCase {
+    @Parameters
+    public static Collection<Object[]> data() {
+        return Arrays.asList(new Object[][] {
+            {CallFailureCause.NONE, 0, true},
+            {CallFailureCause.INVALID_USE, 1, false},
+            {CallFailureCause.IN_EMERGENCY_CALL, 2, false},
+            {CallFailureCause.CANNOT_HOLD_CALL, 3, false},
+            {CallFailureCause.MAX_OUTGOING_CALLS, 4, false},
+            {CallFailureCause.MAX_RINGING_CALLS, 5, false},
+            {CallFailureCause.MAX_HOLD_CALLS, 6, false},
+            {CallFailureCause.MAX_SELF_MANAGED_CALLS, 7, false},
+        });
+    }
+    @Parameter(0) public CallFailureCause e;
+    @Parameter(1) public int code;
+    @Parameter(2) public boolean isSuccess;
+
+    @Test
+    public void testGetCode() {
+        assertEquals(code, e.getCode());
+    }
+
+    @Test
+    public void testIsSuccess() {
+        assertEquals(isSuccess, e.isSuccess());
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java b/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java
index f2fe045..01446d1 100644
--- a/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java
@@ -153,8 +153,9 @@
     }
 
     private void enableUserDefinedCallRedirectionService() {
-        when(mCallRedirectionProcessorHelper.getUserDefinedCallRedirectionService()).thenReturn(
-                USER_DEFINED_SERVICE_TEST_COMPONENT_NAME);
+        when(mCallRedirectionProcessorHelper
+                .getUserDefinedCallRedirectionService(any(UserHandle.class)))
+                .thenReturn(USER_DEFINED_SERVICE_TEST_COMPONENT_NAME);
     }
 
     private void enableCarrierCallRedirectionService() {
@@ -163,8 +164,9 @@
     }
 
     private void disableUserDefinedCallRedirectionService() {
-        when(mCallRedirectionProcessorHelper.getUserDefinedCallRedirectionService()).thenReturn(
-                null);
+        when(mCallRedirectionProcessorHelper
+                .getUserDefinedCallRedirectionService(any(UserHandle.class)))
+                .thenReturn(null);
     }
 
     private void disableCarrierCallRedirectionService() {
@@ -193,9 +195,9 @@
         startProcessWithNoGateWayInfo();
         disableUserDefinedCallRedirectionService();
         disableCarrierCallRedirectionService();
-        mProcessor.performCallRedirection();
+        mProcessor.performCallRedirection(UserHandle.CURRENT);
         verify(mContext, times(0)).bindServiceAsUser(any(Intent.class),
-                any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+                any(ServiceConnection.class), anyInt(), eq(UserHandle.CURRENT));
         verify(mCallsManager, times(1)).onCallRedirectionComplete(eq(mCall), eq(mHandle),
                 eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
                 eq(false), eq(CallRedirectionProcessor.UI_TYPE_NO_ACTION));
@@ -208,9 +210,9 @@
         waitForHandlerAction(mProcessor.getHandler(), HANDLER_TIMEOUT_DELAY);
         disableUserDefinedCallRedirectionService();
         enableCarrierCallRedirectionService();
-        mProcessor.performCallRedirection();
+        mProcessor.performCallRedirection(UserHandle.CURRENT);
         verify(mContext, times(1)).bindServiceAsUser(any(Intent.class),
-                any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+                any(ServiceConnection.class), anyInt(), eq(UserHandle.CURRENT));
         verify(mCallsManager, times(0)).onCallRedirectionComplete(eq(mCall), any(),
                 eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
                 eq(false), eq(CallRedirectionProcessor.UI_TYPE_NO_ACTION));
@@ -228,9 +230,9 @@
         waitForHandlerAction(mProcessor.getHandler(), HANDLER_TIMEOUT_DELAY);
         enableUserDefinedCallRedirectionService();
         disableCarrierCallRedirectionService();
-        mProcessor.performCallRedirection();
+        mProcessor.performCallRedirection(UserHandle.CURRENT);
         verify(mContext, times(1)).bindServiceAsUser(any(Intent.class),
-                any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+                any(ServiceConnection.class), anyInt(), eq(UserHandle.CURRENT));
         verify(mCallsManager, times(0)).onCallRedirectionComplete(eq(mCall), any(),
                 eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
                 eq(false), eq(CallRedirectionProcessor.UI_TYPE_USER_DEFINED_TIMEOUT));
@@ -256,10 +258,10 @@
         waitForHandlerAction(mProcessor.getHandler(), HANDLER_TIMEOUT_DELAY);
         enableUserDefinedCallRedirectionService();
         enableCarrierCallRedirectionService();
-        mProcessor.performCallRedirection();
+        mProcessor.performCallRedirection(UserHandle.CURRENT);
 
         verify(mContext, times(1)).bindServiceAsUser(any(Intent.class),
-                any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+                any(ServiceConnection.class), anyInt(), eq(UserHandle.CURRENT));
         verify(mCallsManager, times(0)).onCallRedirectionComplete(eq(mCall), any(),
                 eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
                 eq(false), eq(CallRedirectionProcessor.UI_TYPE_USER_DEFINED_TIMEOUT));
@@ -294,9 +296,9 @@
         startProcessWithGateWayInfo();
         enableUserDefinedCallRedirectionService();
         enableCarrierCallRedirectionService();
-        mProcessor.performCallRedirection();
+        mProcessor.performCallRedirection(UserHandle.CURRENT);
         verify(mContext, times(1)).bindServiceAsUser(any(Intent.class),
-                any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+                any(ServiceConnection.class), anyInt(), eq(UserHandle.CURRENT));
         verify(mCallsManager, times(0)).onCallRedirectionComplete(eq(mCall), any(),
                 eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
                 eq(false), eq(CallRedirectionProcessor.UI_TYPE_NO_ACTION));
@@ -313,13 +315,13 @@
         enableUserDefinedCallRedirectionService();
         disableCarrierCallRedirectionService();
 
-        mProcessor.performCallRedirection();
+        mProcessor.performCallRedirection(UserHandle.CURRENT);
 
         // Capture binding and mock it out.
         ArgumentCaptor<ServiceConnection> serviceConnectionCaptor = ArgumentCaptor.forClass(
                 ServiceConnection.class);
         verify(mContext, times(1)).bindServiceAsUser(any(Intent.class),
-                serviceConnectionCaptor.capture(), anyInt(), any(UserHandle.class));
+                serviceConnectionCaptor.capture(), anyInt(), eq(UserHandle.CURRENT));
 
         // Mock out a service which performed a redirection
         IBinder mockBinder = mock(IBinder.class);
@@ -361,7 +363,7 @@
         enableUserDefinedCallRedirectionService();
         disableCarrierCallRedirectionService();
 
-        mProcessor.performCallRedirection();
+        mProcessor.performCallRedirection(UserHandle.CURRENT);
 
         // Capture the binder
         ArgumentCaptor<ServiceConnection> serviceConnectionCaptor = ArgumentCaptor.forClass(
diff --git a/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java b/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java
index 09ba47e..d95a0e2 100644
--- a/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java
@@ -41,6 +41,7 @@
 import android.provider.CallLog;
 import android.telecom.CallScreeningService;
 import android.telecom.ParcelableCall;
+import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -104,6 +105,10 @@
                     .setCallScreeningComponentName(COMPONENT_NAME.flattenToString())
                     .build();
 
+    private static final PhoneAccountHandle PA_HANDLE =
+            new PhoneAccountHandle(COMPONENT_NAME,
+                    "pa_id");
+
     @Override
     @Before
     public void setUp() throws Exception {
@@ -124,6 +129,8 @@
 
         when(mCallsManager.getCurrentUserHandle()).thenReturn(UserHandle.CURRENT);
         when(mCall.getId()).thenReturn(CALL_ID);
+        when(mCall.getUserHandleFromTargetPhoneAccount()).
+                thenReturn(PA_HANDLE.getUserHandle());
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         when(mContext.getSystemService(TelecomManager.class))
                 .thenReturn(mTelecomManager);
@@ -132,7 +139,7 @@
         when(mParcelableCallUtilsConverter.toParcelableCall(
                 eq(mCall), anyBoolean(), eq(mPhoneAccountRegistrar))).thenReturn(null);
         when(mContext.bindServiceAsUser(nullable(Intent.class), nullable(ServiceConnection.class),
-                anyInt(), eq(UserHandle.CURRENT))).thenReturn(true);
+                anyInt(), eq(PA_HANDLE.getUserHandle()))).thenReturn(true);
         when(mPackageManager.queryIntentServicesAsUser(nullable(Intent.class), anyInt(), anyInt()))
                 .thenReturn(Collections.singletonList(mResolveInfo));
         doReturn(mCallScreeningService).when(mBinder).queryLocalInterface(anyString());
@@ -154,7 +161,7 @@
     @Test
     public void testContextFailToBind() throws Exception {
         when(mContext.bindServiceAsUser(nullable(Intent.class), nullable(ServiceConnection.class),
-                anyInt(), eq(UserHandle.CURRENT))).thenReturn(false);
+                anyInt(), eq(PA_HANDLE.getUserHandle()))).thenReturn(false);
         CallScreeningServiceFilter filter = new CallScreeningServiceFilter(mCall, PKG_NAME,
                 CallScreeningServiceFilter.PACKAGE_TYPE_CARRIER, mContext, mCallsManager,
                 mAppLabelProxy, mParcelableCallUtilsConverter);
@@ -199,7 +206,7 @@
         when(mPackageManager.checkPermission(Manifest.permission.READ_CONTACTS, PKG_NAME))
                 .thenReturn(PackageManager.PERMISSION_DENIED);
         when(mContext.bindServiceAsUser(nullable(Intent.class), nullable(ServiceConnection.class),
-                anyInt(), eq(UserHandle.CURRENT))).thenThrow(new SecurityException());
+                anyInt(), eq(PA_HANDLE.getUserHandle()))).thenThrow(new SecurityException());
         inputResult.contactExists = true;
         CallScreeningServiceFilter filter = new CallScreeningServiceFilter(mCall, PKG_NAME,
                 CallScreeningServiceFilter.PACKAGE_TYPE_USER_CHOSEN, mContext, mCallsManager,
@@ -397,7 +404,7 @@
                 .bindServiceAsUser(intentCaptor.capture(), serviceCaptor.capture(),
                 eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
                         | Context.BIND_SCHEDULE_LIKE_TOP_APP),
-                eq(UserHandle.CURRENT));
+                eq(PA_HANDLE.getUserHandle()));
 
         Intent capturedIntent = intentCaptor.getValue();
         assertEquals(CallScreeningService.SERVICE_INTERFACE, capturedIntent.getAction());
diff --git a/tests/src/com/android/server/telecom/tests/CallTest.java b/tests/src/com/android/server/telecom/tests/CallTest.java
index d326a29..997e7dd 100644
--- a/tests/src/com/android/server/telecom/tests/CallTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallTest.java
@@ -18,51 +18,66 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
 
 import android.content.ComponentName;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.drawable.ColorDrawable;
 import android.net.Uri;
+import android.os.Bundle;
+import android.telecom.CallAttributes;
+import android.telecom.CallerInfo;
 import android.telecom.Connection;
 import android.telecom.DisconnectCause;
+import android.telecom.ParcelableConference;
+import android.telecom.ParcelableConnection;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
+import android.telecom.StatusHints;
+import android.telecom.TelecomManager;
+import android.telecom.VideoProfile;
+import android.telephony.CallQuality;
 import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Log;
 import android.widget.Toast;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import com.android.server.telecom.Call;
+import com.android.server.telecom.CallIdMapper;
 import com.android.server.telecom.CallState;
 import com.android.server.telecom.CallerInfoLookupHelper;
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.ClockProxy;
 import com.android.server.telecom.ConnectionServiceWrapper;
-import com.android.server.telecom.InCallController;
-import com.android.server.telecom.InCallController.InCallServiceInfo;
 import com.android.server.telecom.PhoneAccountRegistrar;
 import com.android.server.telecom.PhoneNumberUtilsAdapter;
 import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.TransactionalServiceWrapper;
 import com.android.server.telecom.ui.ToastFactory;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 
+import java.util.Collections;
+
 @RunWith(AndroidJUnit4.class)
 public class CallTest extends TelecomTestCase {
     private static final Uri TEST_ADDRESS = Uri.parse("tel:555-1212");
@@ -87,6 +102,7 @@
     @Mock private Toast mMockToast;
     @Mock private PhoneNumberUtilsAdapter mMockPhoneNumberUtilsAdapter;
     @Mock private ConnectionServiceWrapper mMockConnectionService;
+    @Mock private TransactionalServiceWrapper mMockTransactionalService;
 
     private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
 
@@ -95,6 +111,7 @@
         super.setUp();
         doReturn(mMockCallerInfoLookupHelper).when(mMockCallsManager).getCallerInfoLookupHelper();
         doReturn(mMockPhoneAccountRegistrar).when(mMockCallsManager).getPhoneAccountRegistrar();
+        doReturn(0L).when(mMockClockProxy).elapsedRealtime();
         doReturn(SIM_1_ACCOUNT).when(mMockPhoneAccountRegistrar).getPhoneAccountUnchecked(
                 eq(SIM_1_HANDLE));
         doReturn(new ComponentName(mContext, CallTest.class))
@@ -110,23 +127,7 @@
     @Test
     @SmallTest
     public void testSetHasGoneActive() {
-        Call call = new Call(
-                "1", /* callId */
-                mContext,
-                mMockCallsManager,
-                mLock,
-                null /* ConnectionServiceRepository */,
-                mMockPhoneNumberUtilsAdapter,
-                TEST_ADDRESS,
-                null /* GatewayInfo */,
-                null /* connectionManagerPhoneAccountHandle */,
-                SIM_1_HANDLE,
-                Call.CALL_DIRECTION_INCOMING,
-                false /* shouldAttachToExistingConnection*/,
-                false /* isConference */,
-                mMockClockProxy,
-                mMockToastProxy);
-
+        Call call = createCall("1", Call.CALL_DIRECTION_INCOMING);
         assertFalse(call.hasGoneActiveBefore());
         call.setState(CallState.ACTIVE, "");
         assertTrue(call.hasGoneActiveBefore());
@@ -134,9 +135,56 @@
         assertTrue(call.hasGoneActiveBefore());
     }
 
+    /**
+     * Basic tests to check which call states are considered transitory.
+     */
     @Test
     @SmallTest
-    public void testDisconnectCauseWhenAudioProcessing() {
+    public void testIsCallStateTransitory() {
+        assertTrue(CallState.isTransitoryState(CallState.NEW));
+        assertTrue(CallState.isTransitoryState(CallState.CONNECTING));
+        assertTrue(CallState.isTransitoryState(CallState.DISCONNECTING));
+        assertTrue(CallState.isTransitoryState(CallState.ANSWERED));
+
+        assertFalse(CallState.isTransitoryState(CallState.SELECT_PHONE_ACCOUNT));
+        assertFalse(CallState.isTransitoryState(CallState.DIALING));
+        assertFalse(CallState.isTransitoryState(CallState.RINGING));
+        assertFalse(CallState.isTransitoryState(CallState.ACTIVE));
+        assertFalse(CallState.isTransitoryState(CallState.ON_HOLD));
+        assertFalse(CallState.isTransitoryState(CallState.DISCONNECTED));
+        assertFalse(CallState.isTransitoryState(CallState.ABORTED));
+        assertFalse(CallState.isTransitoryState(CallState.PULLING));
+        assertFalse(CallState.isTransitoryState(CallState.AUDIO_PROCESSING));
+        assertFalse(CallState.isTransitoryState(CallState.SIMULATED_RINGING));
+    }
+
+    /**
+     * Basic tests to check which call states are considered intermediate.
+     */
+    @Test
+    @SmallTest
+    public void testIsCallStateIntermediate() {
+        assertTrue(CallState.isIntermediateState(CallState.DIALING));
+        assertTrue(CallState.isIntermediateState(CallState.RINGING));
+
+        assertFalse(CallState.isIntermediateState(CallState.NEW));
+        assertFalse(CallState.isIntermediateState(CallState.CONNECTING));
+        assertFalse(CallState.isIntermediateState(CallState.DISCONNECTING));
+        assertFalse(CallState.isIntermediateState(CallState.ANSWERED));
+        assertFalse(CallState.isIntermediateState(CallState.SELECT_PHONE_ACCOUNT));
+        assertFalse(CallState.isIntermediateState(CallState.ACTIVE));
+        assertFalse(CallState.isIntermediateState(CallState.ON_HOLD));
+        assertFalse(CallState.isIntermediateState(CallState.DISCONNECTED));
+        assertFalse(CallState.isIntermediateState(CallState.ABORTED));
+        assertFalse(CallState.isIntermediateState(CallState.PULLING));
+        assertFalse(CallState.isIntermediateState(CallState.AUDIO_PROCESSING));
+        assertFalse(CallState.isIntermediateState(CallState.SIMULATED_RINGING));
+    }
+
+    @SmallTest
+    @Test
+    public void testIsCreateConnectionComplete() {
+        // A new call with basic info.
         Call call = new Call(
                 "1", /* callId */
                 mContext,
@@ -153,6 +201,22 @@
                 false /* isConference */,
                 mMockClockProxy,
                 mMockToastProxy);
+
+        // To start with connection creation isn't complete.
+        assertFalse(call.isCreateConnectionComplete());
+
+        // Need the bare minimum to get connection creation to complete.
+        ParcelableConnection connection = new ParcelableConnection(null, 0, 0, 0, 0, null, 0, null,
+                0, null, 0, false, false, 0L, 0L, null, null, Collections.emptyList(), null, null,
+                0, 0);
+        call.handleCreateConnectionSuccess(Mockito.mock(CallIdMapper.class), connection);
+        assertTrue(call.isCreateConnectionComplete());
+    }
+
+    @Test
+    @SmallTest
+    public void testDisconnectCauseWhenAudioProcessing() {
+        Call call = createCall("1", Call.CALL_DIRECTION_INCOMING);
         call.setState(CallState.AUDIO_PROCESSING, "");
         call.disconnect();
         call.setDisconnectCause(new DisconnectCause(DisconnectCause.LOCAL));
@@ -162,22 +226,7 @@
     @Test
     @SmallTest
     public void testDisconnectCauseWhenAudioProcessingAfterActive() {
-        Call call = new Call(
-                "1", /* callId */
-                mContext,
-                mMockCallsManager,
-                mLock,
-                null /* ConnectionServiceRepository */,
-                mMockPhoneNumberUtilsAdapter,
-                TEST_ADDRESS,
-                null /* GatewayInfo */,
-                null /* connectionManagerPhoneAccountHandle */,
-                SIM_1_HANDLE,
-                Call.CALL_DIRECTION_INCOMING,
-                false /* shouldAttachToExistingConnection*/,
-                false /* isConference */,
-                mMockClockProxy,
-                mMockToastProxy);
+        Call call = createCall("1", Call.CALL_DIRECTION_INCOMING);
         call.setState(CallState.AUDIO_PROCESSING, "");
         call.setState(CallState.ACTIVE, "");
         call.setState(CallState.AUDIO_PROCESSING, "");
@@ -189,22 +238,7 @@
     @Test
     @SmallTest
     public void testDisconnectCauseWhenSimulatedRingingAndDisconnect() {
-        Call call = new Call(
-                "1", /* callId */
-                mContext,
-                mMockCallsManager,
-                mLock,
-                null /* ConnectionServiceRepository */,
-                mMockPhoneNumberUtilsAdapter,
-                TEST_ADDRESS,
-                null /* GatewayInfo */,
-                null /* connectionManagerPhoneAccountHandle */,
-                SIM_1_HANDLE,
-                Call.CALL_DIRECTION_INCOMING,
-                false /* shouldAttachToExistingConnection*/,
-                false /* isConference */,
-                mMockClockProxy,
-                mMockToastProxy);
+        Call call = createCall("1", Call.CALL_DIRECTION_INCOMING);
         call.setState(CallState.SIMULATED_RINGING, "");
         call.disconnect();
         call.setDisconnectCause(new DisconnectCause(DisconnectCause.LOCAL));
@@ -214,22 +248,7 @@
     @Test
     @SmallTest
     public void testDisconnectCauseWhenSimulatedRingingAndReject() {
-        Call call = new Call(
-                "1", /* callId */
-                mContext,
-                mMockCallsManager,
-                mLock,
-                null /* ConnectionServiceRepository */,
-                mMockPhoneNumberUtilsAdapter,
-                TEST_ADDRESS,
-                null /* GatewayInfo */,
-                null /* connectionManagerPhoneAccountHandle */,
-                SIM_1_HANDLE,
-                Call.CALL_DIRECTION_INCOMING,
-                false /* shouldAttachToExistingConnection*/,
-                false /* isConference */,
-                mMockClockProxy,
-                mMockToastProxy);
+        Call call = createCall("1", Call.CALL_DIRECTION_INCOMING);
         call.setState(CallState.SIMULATED_RINGING, "");
         call.reject(false, "");
         call.setDisconnectCause(new DisconnectCause(DisconnectCause.LOCAL));
@@ -239,22 +258,7 @@
     @Test
     @SmallTest
     public void testCanPullCallRemovedDuringEmergencyCall() {
-        Call call = new Call(
-                "1", /* callId */
-                mContext,
-                mMockCallsManager,
-                mLock,
-                null /* ConnectionServiceRepository */,
-                mMockPhoneNumberUtilsAdapter,
-                TEST_ADDRESS,
-                null /* GatewayInfo */,
-                null /* connectionManagerPhoneAccountHandle */,
-                SIM_1_HANDLE,
-                Call.CALL_DIRECTION_INCOMING,
-                false /* shouldAttachToExistingConnection*/,
-                false /* isConference */,
-                mMockClockProxy,
-                mMockToastProxy);
+        Call call = createCall("1", Call.CALL_DIRECTION_INCOMING);
         boolean[] hasCalledConnectionCapabilitiesChanged = new boolean[1];
         call.addListener(new Call.ListenerBase() {
             @Override
@@ -287,22 +291,7 @@
     @Test
     @SmallTest
     public void testCanNotPullCallDuringEmergencyCall() {
-        Call call = new Call(
-                "1", /* callId */
-                mContext,
-                mMockCallsManager,
-                mLock,
-                null /* ConnectionServiceRepository */,
-                mMockPhoneNumberUtilsAdapter,
-                TEST_ADDRESS,
-                null /* GatewayInfo */,
-                null /* connectionManagerPhoneAccountHandle */,
-                SIM_1_HANDLE,
-                Call.CALL_DIRECTION_INCOMING,
-                false /* shouldAttachToExistingConnection*/,
-                false /* isConference */,
-                mMockClockProxy,
-                mMockToastProxy);
+        Call call = createCall("1", Call.CALL_DIRECTION_INCOMING);
         call.setConnectionService(mMockConnectionService);
         call.setConnectionProperties(Connection.PROPERTY_IS_EXTERNAL_CALL);
         call.setConnectionCapabilities(Connection.CAPABILITY_CAN_PULL_CALL);
@@ -318,6 +307,22 @@
     @Test
     @SmallTest
     public void testCallDirection() {
+        Call call = createCall("1");
+        boolean[] hasCallDirectionChanged = new boolean[1];
+        call.addListener(new Call.ListenerBase() {
+            @Override
+            public void onCallDirectionChanged(Call call) {
+                hasCallDirectionChanged[0] = true;
+            }
+        });
+        assertFalse(call.isIncoming());
+        call.setCallDirection(Call.CALL_DIRECTION_INCOMING);
+        assertTrue(hasCallDirectionChanged[0]);
+        assertTrue(call.isIncoming());
+    }
+
+    @Test
+    public void testIsSuppressedByDoNotDisturbExtra() {
         Call call = new Call(
                 "1", /* callId */
                 mContext,
@@ -334,16 +339,415 @@
                 true /* isConference */,
                 mMockClockProxy,
                 mMockToastProxy);
-        boolean[] hasCallDirectionChanged = new boolean[1];
-        call.addListener(new Call.ListenerBase() {
-            @Override
-            public void onCallDirectionChanged(Call call) {
-                hasCallDirectionChanged[0] = true;
-            }
-        });
-        assertFalse(call.isIncoming());
-        call.setCallDirection(Call.CALL_DIRECTION_INCOMING);
-        assertTrue(hasCallDirectionChanged[0]);
-        assertTrue(call.isIncoming());
+
+        assertFalse(call.wasDndCheckComputedForCall());
+        assertFalse(call.isCallSuppressedByDoNotDisturb());
+        call.setCallIsSuppressedByDoNotDisturb(true);
+        assertTrue(call.wasDndCheckComputedForCall());
+        assertTrue(call.isCallSuppressedByDoNotDisturb());
+    }
+
+    @Test
+    public void testGetConnectionServiceWrapper() {
+        Call call = new Call(
+                "1", /* callId */
+                mContext,
+                mMockCallsManager,
+                mLock,
+                null /* ConnectionServiceRepository */,
+                mMockPhoneNumberUtilsAdapter,
+                TEST_ADDRESS,
+                null /* GatewayInfo */,
+                null /* connectionManagerPhoneAccountHandle */,
+                SIM_1_HANDLE,
+                Call.CALL_DIRECTION_UNDEFINED,
+                false /* shouldAttachToExistingConnection*/,
+                true /* isConference */,
+                mMockClockProxy,
+                mMockToastProxy);
+
+        assertNull(call.getConnectionServiceWrapper());
+        assertFalse(call.isTransactionalCall());
+        call.setConnectionService(mMockConnectionService);
+        assertEquals(mMockConnectionService, call.getConnectionServiceWrapper());
+        call.setIsTransactionalCall(true);
+        assertTrue(call.isTransactionalCall());
+        assertNull(call.getConnectionServiceWrapper());
+        call.setTransactionServiceWrapper(mMockTransactionalService);
+        assertEquals(mMockTransactionalService, call.getTransactionServiceWrapper());
+    }
+
+    @Test
+    public void testCallEventCallbacksWereCalled() {
+        Call call = new Call(
+                "1", /* callId */
+                mContext,
+                mMockCallsManager,
+                mLock,
+                null /* ConnectionServiceRepository */,
+                mMockPhoneNumberUtilsAdapter,
+                TEST_ADDRESS,
+                null /* GatewayInfo */,
+                null /* connectionManagerPhoneAccountHandle */,
+                SIM_1_HANDLE,
+                Call.CALL_DIRECTION_UNDEFINED,
+                false /* shouldAttachToExistingConnection*/,
+                true /* isConference */,
+                mMockClockProxy,
+                mMockToastProxy);
+
+        // setup
+        call.setIsTransactionalCall(true);
+        assertTrue(call.isTransactionalCall());
+        assertNull(call.getConnectionServiceWrapper());
+        call.setTransactionServiceWrapper(mMockTransactionalService);
+        assertEquals(mMockTransactionalService, call.getTransactionServiceWrapper());
+
+        // assert CallEventCallback#onSetInactive is called
+        call.setState(CallState.ACTIVE, "test");
+        call.hold();
+        verify(mMockTransactionalService, times(1)).onSetInactive(call);
+
+        // assert CallEventCallback#onSetActive is called
+        call.setState(CallState.ON_HOLD, "test");
+        call.unhold();
+        verify(mMockTransactionalService, times(1)).onSetActive(call);
+
+        // assert CallEventCallback#onAnswer is called
+        call.setState(CallState.RINGING, "test");
+        call.answer(0);
+        verify(mMockTransactionalService, times(1)).onAnswer(call, 0);
+
+        // assert CallEventCallback#onDisconnect is called
+        call.setState(CallState.ACTIVE, "test");
+        call.disconnect();
+        verify(mMockTransactionalService, times(1)).onDisconnect(call,
+                call.getDisconnectCause());
+    }
+
+    @Test
+    @SmallTest
+    public void testSetConnectionPropertiesRttOnOff() {
+        Call call = createCall("1");
+        call.setConnectionService(mMockConnectionService);
+
+        call.setConnectionProperties(Connection.PROPERTY_IS_RTT);
+        verify(mMockCallsManager).playRttUpgradeToneForCall(any());
+        assertNotNull(null, call.getInCallToCsRttPipeForCs());
+        assertNotNull(null, call.getCsToInCallRttPipeForInCall());
+
+        call.setConnectionProperties(0);
+        assertNull(null, call.getInCallToCsRttPipeForCs());
+        assertNull(null, call.getCsToInCallRttPipeForInCall());
+    }
+
+    @Test
+    @SmallTest
+    public void testGetFromCallerInfo() {
+        Call call = createCall("1");
+
+        CallerInfo info = new CallerInfo();
+        info.setName("name");
+        info.setPhoneNumber("number");
+        info.cachedPhoto = new ColorDrawable();
+        info.cachedPhotoIcon = Bitmap.createBitmap(24, 24, Bitmap.Config.ALPHA_8);
+
+        ArgumentCaptor<CallerInfoLookupHelper.OnQueryCompleteListener> listenerCaptor =
+                ArgumentCaptor.forClass(CallerInfoLookupHelper.OnQueryCompleteListener.class);
+        verify(mMockCallerInfoLookupHelper).startLookup(any(), listenerCaptor.capture());
+        listenerCaptor.getValue().onCallerInfoQueryComplete(call.getHandle(), info);
+
+        assertEquals(info, call.getCallerInfo());
+        assertEquals(info.getName(), call.getName());
+        assertEquals(info.getPhoneNumber(), call.getPhoneNumber());
+        assertEquals(info.cachedPhoto, call.getPhoto());
+        assertEquals(info.cachedPhotoIcon, call.getPhotoIcon());
+        assertEquals(call.getHandle(), call.getContactUri());
+    }
+
+    @Test
+    @SmallTest
+    public void testOriginalCallIntent() {
+        Call call = createCall("1");
+
+        Intent i = new Intent();
+        call.setOriginalCallIntent(i);
+
+        assertEquals(i, call.getOriginalCallIntent());
+    }
+
+    @Test
+    @SmallTest
+    public void testHandleCreateConferenceSuccessNotifiesListeners() {
+        Call.Listener listener = mock(Call.Listener.class);
+
+        Call incomingCall = createCall("1", Call.CALL_DIRECTION_INCOMING);
+        incomingCall.setConnectionService(mMockConnectionService);
+        incomingCall.addListener(listener);
+        Call outgoingCall = createCall("2", Call.CALL_DIRECTION_OUTGOING);
+        outgoingCall.setConnectionService(mMockConnectionService);
+        outgoingCall.addListener(listener);
+
+        StatusHints statusHints = mock(StatusHints.class);
+        Bundle extra = new Bundle();
+        ParcelableConference conference =
+                new ParcelableConference.Builder(SIM_1_HANDLE, Connection.STATE_NEW)
+                    .setAddress(TEST_ADDRESS, TelecomManager.PRESENTATION_ALLOWED)
+                    .setConnectionCapabilities(123)
+                    .setVideoAttributes(null, VideoProfile.STATE_AUDIO_ONLY)
+                    .setRingbackRequested(true)
+                    .setStatusHints(statusHints)
+                    .setExtras(extra)
+                    .build();
+
+        incomingCall.handleCreateConferenceSuccess(null, conference);
+        verify(listener).onSuccessfulIncomingCall(incomingCall);
+
+        outgoingCall.handleCreateConferenceSuccess(null, conference);
+        verify(listener).onSuccessfulOutgoingCall(outgoingCall, CallState.NEW);
+    }
+
+    @Test
+    @SmallTest
+    public void testHandleCreateConferenceSuccess() {
+        Call call = createCall("1", Call.CALL_DIRECTION_INCOMING);
+        call.setConnectionService(mMockConnectionService);
+
+        StatusHints statusHints = mock(StatusHints.class);
+        Bundle extra = new Bundle();
+        ParcelableConference conference =
+                new ParcelableConference.Builder(SIM_1_HANDLE, Connection.STATE_NEW)
+                    .setAddress(TEST_ADDRESS, TelecomManager.PRESENTATION_ALLOWED)
+                    .setConnectionCapabilities(123)
+                    .setVideoAttributes(null, VideoProfile.STATE_AUDIO_ONLY)
+                    .setRingbackRequested(true)
+                    .setStatusHints(statusHints)
+                    .setExtras(extra)
+                    .build();
+
+        call.handleCreateConferenceSuccess(null, conference);
+
+        assertEquals(SIM_1_HANDLE, call.getTargetPhoneAccount());
+        assertEquals(TEST_ADDRESS, call.getHandle());
+        assertEquals(123, call.getConnectionCapabilities());
+        assertNull(call.getVideoProviderProxy());
+        assertEquals(VideoProfile.STATE_AUDIO_ONLY, call.getVideoState());
+        assertTrue(call.isRingbackRequested());
+        assertEquals(statusHints, call.getStatusHints());
+    }
+
+    @Test
+    @SmallTest
+    public void testHandleCreateConferenceFailure() {
+        Call.Listener listener = mock(Call.Listener.class);
+
+        Call incomingCall = createCall("1", Call.CALL_DIRECTION_INCOMING);
+        incomingCall.setConnectionService(mMockConnectionService);
+        incomingCall.addListener(listener);
+        Call outgoingCall = createCall("2", Call.CALL_DIRECTION_OUTGOING);
+        outgoingCall.setConnectionService(mMockConnectionService);
+        outgoingCall.addListener(listener);
+
+        final DisconnectCause cause = new DisconnectCause(DisconnectCause.REJECTED);
+
+        incomingCall.handleCreateConferenceFailure(cause);
+        assertEquals(cause, incomingCall.getDisconnectCause());
+        verify(listener).onFailedIncomingCall(incomingCall);
+
+        outgoingCall.handleCreateConferenceFailure(cause);
+        assertEquals(cause, outgoingCall.getDisconnectCause());
+        verify(listener).onFailedOutgoingCall(outgoingCall, cause);
+    }
+
+    @Test
+    @SmallTest
+    public void testWasConferencePreviouslyMerged() {
+        Call call = createCall("1");
+        call.setConnectionService(mMockConnectionService);
+        call.setConnectionCapabilities(Connection.CAPABILITY_MERGE_CONFERENCE);
+
+        assertFalse(call.wasConferencePreviouslyMerged());
+
+        call.mergeConference();
+
+        assertTrue(call.wasConferencePreviouslyMerged());
+    }
+
+    @Test
+    @SmallTest
+    public void testSwapConference() {
+        Call.Listener listener = mock(Call.Listener.class);
+
+        Call call = createCall("1");
+        call.setConnectionService(mMockConnectionService);
+        call.setConnectionCapabilities(Connection.CAPABILITY_SWAP_CONFERENCE);
+        call.addListener(listener);
+
+        call.swapConference();
+        assertNull(call.getConferenceLevelActiveCall());
+
+        Call childCall1 = createCall("child1");
+        childCall1.setChildOf(call);
+        call.swapConference();
+        assertEquals(childCall1, call.getConferenceLevelActiveCall());
+
+        Call childCall2 = createCall("child2");
+        childCall2.setChildOf(call);
+        call.swapConference();
+        assertEquals(childCall1, call.getConferenceLevelActiveCall());
+        call.swapConference();
+        assertEquals(childCall2, call.getConferenceLevelActiveCall());
+
+        verify(listener, times(4)).onCdmaConferenceSwap(call);
+    }
+
+    @Test
+    @SmallTest
+    public void testHandleCreateConnectionFailure() {
+        Call.Listener listener = mock(Call.Listener.class);
+
+        Call incomingCall = createCall("1", Call.CALL_DIRECTION_INCOMING);
+        incomingCall.setConnectionService(mMockConnectionService);
+        incomingCall.addListener(listener);
+        Call outgoingCall = createCall("2", Call.CALL_DIRECTION_OUTGOING);
+        outgoingCall.setConnectionService(mMockConnectionService);
+        outgoingCall.addListener(listener);
+        Call unknownCall = createCall("3", Call.CALL_DIRECTION_UNKNOWN);
+        unknownCall.setConnectionService(mMockConnectionService);
+        unknownCall.addListener(listener);
+
+        final DisconnectCause cause = new DisconnectCause(DisconnectCause.REJECTED);
+
+        incomingCall.handleCreateConnectionFailure(cause);
+        assertEquals(cause, incomingCall.getDisconnectCause());
+        verify(listener).onFailedIncomingCall(incomingCall);
+
+        outgoingCall.handleCreateConnectionFailure(cause);
+        assertEquals(cause, outgoingCall.getDisconnectCause());
+        verify(listener).onFailedOutgoingCall(outgoingCall, cause);
+
+        unknownCall.handleCreateConnectionFailure(cause);
+        assertEquals(cause, unknownCall.getDisconnectCause());
+        verify(listener).onFailedUnknownCall(unknownCall);
+    }
+
+    /**
+     * ensure a Call object does not throw an NPE when the CallingPackageIdentity is not set and
+     * the correct values are returned when set
+     */
+    @Test
+    @SmallTest
+    public void testCallingPackageIdentity() {
+        final int packageUid = 123;
+        final int packagePid = 1;
+
+        Call call = createCall("1");
+
+        // assert default values for a Calls CallingPackageIdentity are -1 unless set via the setter
+        assertEquals(-1, call.getCallingPackageIdentity().mCallingPackageUid);
+        assertEquals(-1, call.getCallingPackageIdentity().mCallingPackagePid);
+
+        // set the Call objects CallingPackageIdentity via the setter and a bundle
+        Bundle extras = new Bundle();
+        extras.putInt(CallAttributes.CALLER_UID_KEY, packageUid);
+        extras.putInt(CallAttributes.CALLER_PID_KEY, packagePid);
+        // assert that the setter removed the extras
+        assertEquals(packageUid, extras.getInt(CallAttributes.CALLER_UID_KEY));
+        assertEquals(packagePid, extras.getInt(CallAttributes.CALLER_PID_KEY));
+        call.setCallingPackageIdentity(extras);
+        // assert that the setter removed the extras
+        assertEquals(0, extras.getInt(CallAttributes.CALLER_UID_KEY));
+        assertEquals(0, extras.getInt(CallAttributes.CALLER_PID_KEY));
+        // assert the properties are fetched correctly
+        assertEquals(packageUid, call.getCallingPackageIdentity().mCallingPackageUid);
+        assertEquals(packagePid, call.getCallingPackageIdentity().mCallingPackagePid);
+    }
+
+        @Test
+    @SmallTest
+    public void testOnConnectionEventNotifiesListener() {
+        Call.Listener listener = mock(Call.Listener.class);
+        Call call = createCall("1");
+        call.addListener(listener);
+
+        call.onConnectionEvent(Connection.EVENT_ON_HOLD_TONE_START, null);
+        verify(listener).onHoldToneRequested(call);
+        assertTrue(call.isRemotelyHeld());
+
+        call.onConnectionEvent(Connection.EVENT_ON_HOLD_TONE_END, null);
+        verify(listener, times(2)).onHoldToneRequested(call);
+        assertFalse(call.isRemotelyHeld());
+
+        call.onConnectionEvent(Connection.EVENT_CALL_HOLD_FAILED, null);
+        verify(listener).onCallHoldFailed(call);
+
+        call.onConnectionEvent(Connection.EVENT_CALL_SWITCH_FAILED, null);
+        verify(listener).onCallSwitchFailed(call);
+
+        final int d2dType = 1;
+        final int d2dValue = 2;
+        final Bundle d2dExtras = new Bundle();
+        d2dExtras.putInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_TYPE, d2dType);
+        d2dExtras.putInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_VALUE, d2dValue);
+        call.onConnectionEvent(Connection.EVENT_DEVICE_TO_DEVICE_MESSAGE, d2dExtras);
+        verify(listener).onReceivedDeviceToDeviceMessage(call, d2dType, d2dValue);
+
+        final CallQuality quality = new CallQuality();
+        final Bundle callQualityExtras = new Bundle();
+        callQualityExtras.putParcelable(Connection.EXTRA_CALL_QUALITY_REPORT, quality);
+        call.onConnectionEvent(Connection.EVENT_CALL_QUALITY_REPORT, callQualityExtras);
+        verify(listener).onReceivedCallQualityReport(call, quality);
+    }
+
+    @Test
+    @SmallTest
+    public void testDiagnosticMessage() {
+        Call.Listener listener = mock(Call.Listener.class);
+        Call call = createCall("1");
+        call.addListener(listener);
+
+        final int id = 1;
+        final String message = "msg";
+
+        call.displayDiagnosticMessage(id, message);
+        verify(listener).onConnectionEvent(
+                eq(call),
+                eq(android.telecom.Call.EVENT_DISPLAY_DIAGNOSTIC_MESSAGE),
+                argThat(extras -> {
+                    return extras.getInt(android.telecom.Call.EXTRA_DIAGNOSTIC_MESSAGE_ID) == id &&
+                            extras.getCharSequence(android.telecom.Call.EXTRA_DIAGNOSTIC_MESSAGE)
+                                .toString().equals(message);
+                }));
+
+        call.clearDiagnosticMessage(id);
+        verify(listener).onConnectionEvent(
+                eq(call),
+                eq(android.telecom.Call.EVENT_CLEAR_DIAGNOSTIC_MESSAGE),
+                argThat(extras -> {
+                    return extras.getInt(android.telecom.Call.EXTRA_DIAGNOSTIC_MESSAGE_ID) == id;
+                }));
+    }
+
+    private Call createCall(String id) {
+        return createCall(id, Call.CALL_DIRECTION_UNDEFINED);
+    }
+
+    private Call createCall(String id, int callDirection) {
+        return new Call(
+                id,
+                mContext,
+                mMockCallsManager,
+                mLock,
+                null,
+                mMockPhoneNumberUtilsAdapter,
+                TEST_ADDRESS,
+                null /* GatewayInfo */,
+                null,
+                SIM_1_HANDLE,
+                callDirection,
+                false,
+                false,
+                mMockClockProxy,
+                mMockToastProxy);
     }
 }
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index 160114a..129bba2 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -42,17 +42,25 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.Manifest;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.media.AudioManager;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.OutcomeReceiver;
 import android.os.Process;
+import android.os.ResultReceiver;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.provider.BlockedNumberContract;
+import android.telecom.CallException;
+import android.telecom.CallScreeningService;
 import android.telecom.CallerInfo;
 import android.telecom.Connection;
 import android.telecom.DisconnectCause;
@@ -61,18 +69,24 @@
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
 import android.telecom.VideoProfile;
+import android.telephony.CarrierConfigManager;
+import android.telephony.PhoneCapability;
 import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Pair;
 import android.widget.Toast;
 
+import com.android.server.telecom.AnomalyReporterAdapter;
 import com.android.server.telecom.AsyncRingtonePlayer;
 import com.android.server.telecom.Call;
+import com.android.server.telecom.CallAnomalyWatchdog;
 import com.android.server.telecom.CallAudioManager;
 import com.android.server.telecom.CallAudioModeStateMachine;
 import com.android.server.telecom.CallAudioRouteStateMachine;
 import com.android.server.telecom.CallDiagnosticServiceController;
+import com.android.server.telecom.CallEndpointController;
+import com.android.server.telecom.CallEndpointControllerFactory;
 import com.android.server.telecom.CallState;
 import com.android.server.telecom.CallerInfoLookupHelper;
 import com.android.server.telecom.CallsManager;
@@ -81,7 +95,9 @@
 import com.android.server.telecom.ConnectionServiceFocusManager.ConnectionServiceFocusManagerFactory;
 import com.android.server.telecom.ConnectionServiceWrapper;
 import com.android.server.telecom.DefaultDialerCache;
+import com.android.server.telecom.EmergencyCallDiagnosticLogger;
 import com.android.server.telecom.EmergencyCallHelper;
+import com.android.server.telecom.HandoverState;
 import com.android.server.telecom.HeadsetMediaButton;
 import com.android.server.telecom.HeadsetMediaButtonFactory;
 import com.android.server.telecom.InCallController;
@@ -94,6 +110,7 @@
 import com.android.server.telecom.PhoneNumberUtilsAdapter;
 import com.android.server.telecom.ProximitySensorManager;
 import com.android.server.telecom.ProximitySensorManagerFactory;
+import com.android.server.telecom.Ringer;
 import com.android.server.telecom.RoleManagerAdapter;
 import com.android.server.telecom.SystemStateHelper;
 import com.android.server.telecom.TelecomSystem;
@@ -101,10 +118,12 @@
 import com.android.server.telecom.WiredHeadsetManager;
 import com.android.server.telecom.bluetooth.BluetoothRouteManager;
 import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
+import com.android.server.telecom.callfiltering.BlockedNumbersAdapter;
 import com.android.server.telecom.callfiltering.CallFilteringResult;
 import com.android.server.telecom.ui.AudioProcessingNotification;
 import com.android.server.telecom.ui.DisconnectedCallNotifier;
 import com.android.server.telecom.ui.ToastFactory;
+import com.android.server.telecom.voip.TransactionManager;
 
 import org.junit.After;
 import org.junit.Before;
@@ -120,7 +139,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CompletableFuture;
@@ -153,6 +171,13 @@
                     | PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)
             .setIsEnabled(true)
             .build();
+    private static final PhoneAccount SIM_1_ACCOUNT_SECONDARY = new PhoneAccount
+            .Builder(SIM_1_HANDLE_SECONDARY, "Sim1")
+            .setCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION
+                    | PhoneAccount.CAPABILITY_CALL_PROVIDER
+                    | PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)
+            .setIsEnabled(true)
+            .build();
     private static final PhoneAccount SIM_2_ACCOUNT = new PhoneAccount.Builder(SIM_2_HANDLE, "Sim2")
             .setCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION
                     | PhoneAccount.CAPABILITY_CALL_PROVIDER
@@ -171,6 +196,8 @@
             TEST_ADDRESS2, SIM_1_HANDLE,
             TEST_ADDRESS3, SIM_2_HANDLE);
 
+    private static final String DEFAULT_CALL_SCREENING_APP = "com.foo.call_screen_app";
+
     private static int sCallId = 1;
     private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
     @Mock private CallerInfoLookupHelper mCallerInfoLookupHelper;
@@ -199,6 +226,8 @@
     @Mock private AudioProcessingNotification mAudioProcessingNotification;
     @Mock private InCallControllerFactory mInCallControllerFactory;
     @Mock private InCallController mInCallController;
+    @Mock private CallEndpointControllerFactory mCallEndpointControllerFactory;
+    @Mock private CallEndpointController mCallEndpointController;
     @Mock private ConnectionServiceFocusManager mConnectionSvrFocusMgr;
     @Mock private CallAudioRouteStateMachine mCallAudioRouteStateMachine;
     @Mock private CallAudioRouteStateMachine.Factory mCallAudioRouteStateMachineFactory;
@@ -209,6 +238,13 @@
     @Mock private RoleManagerAdapter mRoleManagerAdapter;
     @Mock private ToastFactory mToastFactory;
     @Mock private Toast mToast;
+    @Mock private CallAnomalyWatchdog mCallAnomalyWatchdog;
+
+    @Mock private EmergencyCallDiagnosticLogger mEmergencyCallDiagnosticLogger;
+    @Mock private AnomalyReporterAdapter mAnomalyReporterAdapter;
+    @Mock private Ringer.AccessibilityManagerAdapter mAccessibilityManagerAdapter;
+    @Mock private BlockedNumbersAdapter mBlockedNumbersAdapter;
+    @Mock private PhoneCapability mPhoneCapability;
 
     private CallsManager mCallsManager;
 
@@ -225,8 +261,10 @@
                 mProximitySensorManager);
         when(mInCallControllerFactory.create(any(), any(), any(), any(), any(), any(),
                 any())).thenReturn(mInCallController);
+        when(mCallEndpointControllerFactory.create(any(), any(), any())).thenReturn(
+                mCallEndpointController);
         when(mCallAudioRouteStateMachineFactory.create(any(), any(), any(), any(), any(), any(),
-                anyInt())).thenReturn(mCallAudioRouteStateMachine);
+                anyInt(), any())).thenReturn(mCallAudioRouteStateMachine);
         when(mCallAudioModeStateMachineFactory.create(any(), any()))
                 .thenReturn(mCallAudioModeStateMachine);
         when(mClockProxy.currentTimeMillis()).thenReturn(System.currentTimeMillis());
@@ -266,7 +304,15 @@
                 mInCallControllerFactory,
                 mCallDiagnosticServiceController,
                 mRoleManagerAdapter,
-                mToastFactory);
+                mToastFactory,
+                mCallEndpointControllerFactory,
+                mCallAnomalyWatchdog,
+                mAccessibilityManagerAdapter,
+                // Just do async tasks synchronously to support testing.
+                command -> command.run(),
+                mBlockedNumbersAdapter,
+                TransactionManager.getTestInstance(),
+                mEmergencyCallDiagnosticLogger);
 
         when(mPhoneAccountRegistrar.getPhoneAccount(
                 eq(SELF_MANAGED_HANDLE), any())).thenReturn(SELF_MANAGED_ACCOUNT);
@@ -291,6 +337,26 @@
         assertEquals(0, mCallsManager.constructPossiblePhoneAccounts(null, null, false, false).size());
     }
 
+    private Call constructOngoingCall(String callId, PhoneAccountHandle phoneAccountHandle) {
+        Call ongoingCall = new Call(
+                callId,
+                mContext,
+                mCallsManager,
+                mLock,
+                null /* ConnectionServiceRepository */,
+                mPhoneNumberUtilsAdapter,
+                TEST_ADDRESS,
+                null /* GatewayInfo */,
+                null /* connectionManagerPhoneAccountHandle */,
+                phoneAccountHandle,
+                Call.CALL_DIRECTION_INCOMING,
+                false /* shouldAttachToExistingConnection*/,
+                false /* isConference */,
+                mClockProxy,
+                mToastFactory);
+        ongoingCall.setState(CallState.ACTIVE, "just cuz");
+        return ongoingCall;
+    }
     /**
      * Verify behavior for multisim devices where we want to ensure that the active sim is used for
      * placing a new call.
@@ -301,23 +367,7 @@
     public void testConstructPossiblePhoneAccountsMultiSimActive() throws Exception {
         setupMsimAccounts();
 
-        Call ongoingCall = new Call(
-                "1", /* callId */
-                mContext,
-                mCallsManager,
-                mLock,
-                null /* ConnectionServiceRepository */,
-                mPhoneNumberUtilsAdapter,
-                TEST_ADDRESS,
-                null /* GatewayInfo */,
-                null /* connectionManagerPhoneAccountHandle */,
-                SIM_2_HANDLE,
-                Call.CALL_DIRECTION_INCOMING,
-                false /* shouldAttachToExistingConnection*/,
-                false /* isConference */,
-                mClockProxy,
-                mToastFactory);
-        ongoingCall.setState(CallState.ACTIVE, "just cuz");
+        Call ongoingCall = constructOngoingCall("1", SIM_2_HANDLE);
         mCallsManager.addCall(ongoingCall);
 
         List<PhoneAccountHandle> phoneAccountHandles = mCallsManager.constructPossiblePhoneAccounts(
@@ -340,6 +390,47 @@
         assertEquals(2, phoneAccountHandles.size());
     }
 
+    /**
+     * For DSDA-enabled multisim devices with an ongoing call, verify that both SIMs'
+     * PhoneAccountHandles are constructed while placing a new call.
+     * @throws Exception
+     */
+    @MediumTest
+    @Test
+    public void testConstructPossiblePhoneAccountsMultiSimActive_dsdaCallingPossible() throws
+            Exception {
+        setupMsimAccounts();
+        setMaxActiveVoiceSubscriptions(2);
+
+        Call ongoingCall = constructOngoingCall("1", SIM_2_HANDLE);
+        mCallsManager.addCall(ongoingCall);
+
+        List<PhoneAccountHandle> phoneAccountHandles = mCallsManager.constructPossiblePhoneAccounts(
+                TEST_ADDRESS, null, false, false);
+        assertEquals(2, phoneAccountHandles.size());
+    }
+
+    /**
+     * For DSDA-enabled multisim devices with an ongoing call, verify that only the active SIMs'
+     * PhoneAccountHandle is constructed while placing an emergency call.
+     * @throws Exception
+     */
+    @MediumTest
+    @Test
+    public void testConstructPossiblePhoneAccountsMultiSimActive_dsdaCallingPossible_emergencyCall()
+            throws Exception {
+        setupMsimAccounts();
+        setMaxActiveVoiceSubscriptions(2);
+
+        Call ongoingCall = constructOngoingCall("1", SIM_2_HANDLE);
+        mCallsManager.addCall(ongoingCall);
+
+        List<PhoneAccountHandle> phoneAccountHandles = mCallsManager.constructPossiblePhoneAccounts(
+                TEST_ADDRESS, null, false, true /* isEmergency */);
+        assertEquals(1, phoneAccountHandles.size());
+        assertEquals(SIM_2_HANDLE, phoneAccountHandles.get(0));
+    }
+
     private void setupCallerInfoLookupHelper() {
         doAnswer(invocation -> {
             Uri handle = invocation.getArgument(0);
@@ -383,7 +474,7 @@
         when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
                 SIM_1_HANDLE);
         when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
-                any(), anyInt(), anyInt())).thenReturn(
+                any(), anyInt(), anyInt(), anyBoolean())).thenReturn(
                 new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
 
         List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
@@ -407,7 +498,7 @@
         when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
                 null);
         when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
-                any(), anyInt(), anyInt())).thenReturn(
+                any(), anyInt(), anyInt(), anyBoolean())).thenReturn(
                 new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
 
         List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
@@ -431,8 +522,8 @@
         when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
                 null);
         when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
-                any(), eq(PhoneAccount.CAPABILITY_VIDEO_CALLING), anyInt())).thenReturn(
-                new ArrayList<>(Arrays.asList(SIM_2_HANDLE)));
+                any(), eq(PhoneAccount.CAPABILITY_VIDEO_CALLING), anyInt(), anyBoolean()))
+                .thenReturn(new ArrayList<>(Arrays.asList(SIM_2_HANDLE)));
 
         List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
                 null /* phoneAcct */, TEST_ADDRESS, true /* isVideo */, false /* isEmergency */, null /* userHandle */)
@@ -455,11 +546,11 @@
                 null);
         // When querying for video capable accounts, return nothing.
         when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
-                any(), eq(PhoneAccount.CAPABILITY_VIDEO_CALLING), anyInt())).thenReturn(
-                Collections.emptyList());
+                any(), eq(PhoneAccount.CAPABILITY_VIDEO_CALLING), anyInt(), anyBoolean())).
+                thenReturn(Collections.emptyList());
         // When querying for non-video capable accounts, return one.
         when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
-                any(), eq(0 /* none specified */), anyInt())).thenReturn(
+                any(), eq(0 /* none specified */), anyInt(), anyBoolean())).thenReturn(
                 new ArrayList<>(Arrays.asList(SIM_1_HANDLE)));
         List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
                 null /* phoneAcct */, TEST_ADDRESS, true /* isVideo */, false /* isEmergency */, null /* userHandle */)
@@ -481,7 +572,7 @@
         when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
                 null);
         when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
-                any(), anyInt(), anyInt())).thenReturn(
+                any(), anyInt(), anyInt(), anyBoolean())).thenReturn(
                 new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
 
         List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
@@ -502,7 +593,7 @@
         when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
                 null);
         when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
-                any(), anyInt(), anyInt())).thenReturn(
+                any(), anyInt(), anyInt(), anyBoolean())).thenReturn(
                 new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
 
         List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
@@ -597,6 +688,54 @@
         verify(heldCall).unhold(any());
     }
 
+    /**
+     * Ensures we don't auto-unhold a call from a different app when we locally disconnect a call.
+     */
+    @SmallTest
+    @Test
+    public void testDontUnholdCallsBetweenConnectionServices() {
+        // GIVEN a CallsManager with ongoing call
+        Call ongoingCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
+        when(ongoingCall.isDisconnectHandledViaFuture()).thenReturn(false);
+        doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
+        doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
+        when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
+
+        // and a held call which has different ConnectionService
+        Call heldCall = addSpyCall(VOIP_1_HANDLE, CallState.ON_HOLD);
+
+        // Disconnect and cleanup the active ongoing call.
+        mCallsManager.disconnectCall(ongoingCall);
+        mCallsManager.markCallAsRemoved(ongoingCall);
+
+        // Should not unhold the held call since its in another app.
+        verify(heldCall, never()).unhold();
+    }
+
+    /**
+     * Ensures we do auto-unhold a call from the same app when we locally disconnect a call.
+     */
+    @SmallTest
+    @Test
+    public void testUnholdCallWhenDisconnectingInSameApp() {
+        // GIVEN a CallsManager with ongoing call
+        Call ongoingCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
+        when(ongoingCall.isDisconnectHandledViaFuture()).thenReturn(false);
+        doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_HOLD);
+        doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
+        when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
+
+        // and a held call which has same ConnectionService
+        Call heldCall = addSpyCall(SIM_1_HANDLE, CallState.ON_HOLD);
+
+        // Disconnect and cleanup the active ongoing call.
+        mCallsManager.disconnectCall(ongoingCall);
+        mCallsManager.markCallAsRemoved(ongoingCall);
+
+        // Should auto-unhold the held call since its in the same app.
+        verify(heldCall).unhold();
+    }
+
     @SmallTest
     @Test
     public void testUnholdCallWhenOngoingEmergCallCanNotBeHeldAndFromDifferentConnectionService() {
@@ -942,6 +1081,43 @@
 
     @SmallTest
     @Test
+    public void testNoFilteringOfNetworkIdentifiedEmergencyCalls() {
+        // GIVEN an incoming call which is network identified as an emergency call.
+        Call incomingCall = addSpyCall(CallState.NEW);
+        incomingCall.setConnectionProperties(Connection.PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL);
+        doReturn(false).when(incomingCall).can(Connection.CAPABILITY_HOLD);
+        doReturn(false).when(incomingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
+        doReturn(true).when(incomingCall)
+                .hasProperty(Connection.PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL);
+        doReturn(true).when(incomingCall).setState(anyInt(), any());
+
+        // WHEN the incoming call is successfully added.
+        mCallsManager.onSuccessfulIncomingCall(incomingCall);
+
+        // THEN the incoming call is not using call filtering
+        verify(incomingCall).setIsUsingCallFiltering(eq(false));
+    }
+
+    @SmallTest
+    @Test
+    public void testNoFilteringOfEmergencySmsModeCalls() {
+        // GIVEN an incoming call which is network identified as an emergency call.
+        Call incomingCall = addSpyCall(CallState.NEW);
+        when(mComponentContextFixture.getTelephonyManager().isInEmergencySmsMode())
+                .thenReturn(true);
+        doReturn(false).when(incomingCall).can(Connection.CAPABILITY_HOLD);
+        doReturn(false).when(incomingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
+        doReturn(true).when(incomingCall).setState(anyInt(), any());
+
+        // WHEN the incoming call is successfully added.
+        mCallsManager.onSuccessfulIncomingCall(incomingCall);
+
+        // THEN the incoming call is not using call filtering
+        verify(incomingCall).setIsUsingCallFiltering(eq(false));
+    }
+
+    @SmallTest
+    @Test
     public void testAcceptIncomingCallWhenHeadsetMediaButtonShortPress() {
         // GIVEN an incoming call
         Call incomingCall = addSpyCall();
@@ -1141,7 +1317,7 @@
         when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
                 SIM_1_HANDLE);
         when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
-                any(), anyInt(), anyInt())).thenReturn(
+                any(), anyInt(), anyInt(), anyBoolean())).thenReturn(
                 new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
         mCallsManager.addConnectionServiceRepositoryCache(SIM_2_HANDLE.getComponentName(),
                 SIM_2_HANDLE.getUserHandle(), service);
@@ -1169,13 +1345,132 @@
         when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(SIM_1_HANDLE))
                 .thenReturn(SIM_1_ACCOUNT);
 
-        assertFalse(mCallsManager.isIncomingCallPermitted(null, SELF_MANAGED_HANDLE));
-        assertFalse(mCallsManager.isIncomingCallPermitted(null, SIM_1_HANDLE));
+        assertFalse(mCallsManager.isIncomingCallPermitted(SELF_MANAGED_HANDLE));
+        assertFalse(mCallsManager.isIncomingCallPermitted(SIM_1_HANDLE));
+    }
+
+    @MediumTest
+    @Test
+    public void testManagedIncomingCallPermitted() {
+        when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(SIM_1_HANDLE))
+                .thenReturn(SIM_1_ACCOUNT);
+
+        // Don't care
+        Call selfManagedCall = addSpyCall(SELF_MANAGED_HANDLE, CallState.ACTIVE);
+        when(selfManagedCall.isSelfManaged()).thenReturn(true);
+        assertTrue(mCallsManager.isIncomingCallPermitted(SIM_1_HANDLE));
+
+        Call existingCall = addSpyCall(SIM_1_HANDLE, CallState.NEW);
+        when(existingCall.isSelfManaged()).thenReturn(false);
+
+        when(existingCall.getState()).thenReturn(CallState.RINGING);
+        assertFalse(mCallsManager.isIncomingCallPermitted(SIM_1_HANDLE));
+
+        when(existingCall.getState()).thenReturn(CallState.ON_HOLD);
+        assertFalse(mCallsManager.isIncomingCallPermitted(SIM_1_HANDLE));
+    }
+
+    @MediumTest
+    @Test
+    public void testSelfManagedIncomingCallPermitted() {
+        when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(SELF_MANAGED_HANDLE))
+                .thenReturn(SELF_MANAGED_ACCOUNT);
+
+        // Don't care
+        Call managedCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
+        when(managedCall.isSelfManaged()).thenReturn(false);
+        assertTrue(mCallsManager.isIncomingCallPermitted(SELF_MANAGED_HANDLE));
+
+        Call existingCall = addSpyCall(SELF_MANAGED_HANDLE, CallState.RINGING);
+        when(existingCall.isSelfManaged()).thenReturn(true);
+        assertFalse(mCallsManager.isIncomingCallPermitted(SELF_MANAGED_HANDLE));
+
+        when(existingCall.getState()).thenReturn(CallState.ACTIVE);
+        assertTrue(mCallsManager.isIncomingCallPermitted(SELF_MANAGED_HANDLE));
+
+        // Add self managed calls up to 10
+        for (int i = 0; i < 9; i++) {
+            Call selfManagedCall = addSpyCall(SELF_MANAGED_HANDLE, CallState.ON_HOLD);
+            when(selfManagedCall.isSelfManaged()).thenReturn(true);
+        }
+        assertFalse(mCallsManager.isIncomingCallPermitted(SELF_MANAGED_HANDLE));
     }
 
     @SmallTest
     @Test
-    public void testMakeRoomForOutgoingCallAudioProcessingInProgress() {
+    public void testManagedOutgoingCallPermitted() {
+        when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(SIM_1_HANDLE))
+                .thenReturn(SIM_1_ACCOUNT);
+
+        // Don't care
+        Call selfManagedCall = addSpyCall(SELF_MANAGED_HANDLE, CallState.ACTIVE);
+        when(selfManagedCall.isSelfManaged()).thenReturn(true);
+        assertTrue(mCallsManager.isOutgoingCallPermitted(SIM_1_HANDLE));
+
+        Call existingCall = addSpyCall(SIM_1_HANDLE, CallState.NEW);
+        when(existingCall.isSelfManaged()).thenReturn(false);
+
+        when(existingCall.getState()).thenReturn(CallState.CONNECTING);
+        assertFalse(mCallsManager.isOutgoingCallPermitted(SIM_1_HANDLE));
+
+        when(existingCall.getState()).thenReturn(CallState.DIALING);
+        assertFalse(mCallsManager.isOutgoingCallPermitted(SIM_1_HANDLE));
+
+        when(existingCall.getState()).thenReturn(CallState.ACTIVE);
+        assertFalse(mCallsManager.isOutgoingCallPermitted(SIM_1_HANDLE));
+
+        when(existingCall.getState()).thenReturn(CallState.ON_HOLD);
+        assertFalse(mCallsManager.isOutgoingCallPermitted(SIM_1_HANDLE));
+    }
+
+    @SmallTest
+    @Test
+    public void testSelfManagedOutgoingCallPermitted() {
+        when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(SELF_MANAGED_HANDLE))
+                .thenReturn(SELF_MANAGED_ACCOUNT);
+
+        // Don't care
+        Call managedCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
+        when(managedCall.isSelfManaged()).thenReturn(false);
+        assertTrue(mCallsManager.isOutgoingCallPermitted(SELF_MANAGED_HANDLE));
+
+        Call ongoingCall = addSpyCall(SELF_MANAGED_HANDLE, CallState.ACTIVE);
+        when(ongoingCall.isSelfManaged()).thenReturn(true);
+        when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
+
+        when(ongoingCall.can(Connection.CAPABILITY_HOLD)).thenReturn(false);
+        assertFalse(mCallsManager.isOutgoingCallPermitted(SELF_MANAGED_HANDLE));
+
+        when(ongoingCall.can(Connection.CAPABILITY_HOLD)).thenReturn(true);
+        assertTrue(mCallsManager.isOutgoingCallPermitted(SELF_MANAGED_HANDLE));
+
+        Call handoverCall = addSpyCall(SELF_MANAGED_HANDLE, CallState.NEW);
+        when(handoverCall.isSelfManaged()).thenReturn(true);
+        when(handoverCall.getHandoverSourceCall()).thenReturn(mock(Call.class));
+        assertTrue(mCallsManager.isOutgoingCallPermitted(handoverCall, SELF_MANAGED_HANDLE));
+
+        // Add self managed calls up to 10
+        for (int i = 0; i < 8; i++) {
+            Call selfManagedCall = addSpyCall(SELF_MANAGED_HANDLE, CallState.ON_HOLD);
+            when(selfManagedCall.isSelfManaged()).thenReturn(true);
+        }
+        assertFalse(mCallsManager.isOutgoingCallPermitted(SELF_MANAGED_HANDLE));
+    }
+
+    @SmallTest
+    @Test
+    public void testSelfManagedOutgoingCallPermittedHasEmergencyCall() {
+        when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(SELF_MANAGED_HANDLE))
+                .thenReturn(SELF_MANAGED_ACCOUNT);
+
+        Call emergencyCall = addSpyCall();
+        when(emergencyCall.isEmergencyCall()).thenReturn(true);
+        assertFalse(mCallsManager.isOutgoingCallPermitted(SELF_MANAGED_HANDLE));
+    }
+
+    @SmallTest
+    @Test
+    public void testMakeRoomForEmergencyCallAudioProcessingInProgress() {
         Call ongoingCall = addSpyCall(SIM_2_HANDLE, CallState.AUDIO_PROCESSING);
 
         Call newEmergencyCall = createCall(SIM_1_HANDLE, CallState.NEW);
@@ -1190,7 +1485,7 @@
 
     @SmallTest
     @Test
-    public void testMakeRoomForEmergencyDuringIncomingCall() {
+    public void testMakeRoomForEmergencyCallDuringIncomingCall() {
         Call ongoingCall = addSpyCall(SIM_2_HANDLE, CallState.RINGING);
 
         Call newEmergencyCall = createCall(SIM_1_HANDLE, CallState.NEW);
@@ -1253,21 +1548,156 @@
         verify(ringingCall).reject(anyBoolean(), any(), any());
     }
 
+    /**
+     * Verifies that an anomaly report is triggered when a stuck/zombie call is found and force
+     * disconnected when making room for an outgoing call.
+     */
     @SmallTest
     @Test
-    public void testMakeRoomForOutgoingCallConnecting() {
+    public void testAnomalyReportedWhenMakeRoomForOutgoingCallConnecting() {
+        mCallsManager.setAnomalyReporterAdapter(mAnomalyReporterAdapter);
         Call ongoingCall = addSpyCall(SIM_2_HANDLE, CallState.CONNECTING);
 
         Call newCall = createCall(SIM_1_HANDLE, CallState.NEW);
         when(mComponentContextFixture.getTelephonyManager().isEmergencyNumber(any()))
                 .thenReturn(false);
-        newCall.setHandle(Uri.fromParts("tel", "5551213", null),
-                TelecomManager.PRESENTATION_ALLOWED);
+        newCall.setHandle(TEST_ADDRESS, TelecomManager.PRESENTATION_ALLOWED);
+
+        assertTrue(mCallsManager.makeRoomForOutgoingCall(newCall));
+        verify(mAnomalyReporterAdapter).reportAnomaly(
+                CallsManager.LIVE_CALL_STUCK_CONNECTING_ERROR_UUID,
+                CallsManager.LIVE_CALL_STUCK_CONNECTING_ERROR_MSG);
+        verify(ongoingCall).disconnect(anyLong(), anyString());
+    }
+
+    @SmallTest
+    @Test
+    public void testMakeRoomForEmergencyCallHasOutgoingCall() {
+        Call outgoingCall = addSpyCall(SIM_1_HANDLE, CallState.CONNECTING);
+        when(outgoingCall.isEmergencyCall()).thenReturn(false);
+
+        Call newEmergencyCall = createSpyCall(SIM_1_HANDLE, CallState.NEW);
+        when(newEmergencyCall.isEmergencyCall()).thenReturn(true);
+
+        assertTrue(mCallsManager.makeRoomForOutgoingEmergencyCall(newEmergencyCall));
+        verify(outgoingCall).disconnect(anyString());
+    }
+
+    @SmallTest
+    @Test
+    public void testMakeRoomForEmergencyCallHasOutgoingEmergencyCall() {
+        Call outgoingCall = addSpyCall(SIM_1_HANDLE, CallState.CONNECTING);
+        when(outgoingCall.isEmergencyCall()).thenReturn(true);
+
+        Call newEmergencyCall = createSpyCall(SIM_1_HANDLE, CallState.NEW);
+        when(newEmergencyCall.isEmergencyCall()).thenReturn(true);
+
+        assertFalse(mCallsManager.makeRoomForOutgoingEmergencyCall(newEmergencyCall));
+        verify(outgoingCall, never()).disconnect(anyString());
+    }
+
+    @SmallTest
+    @Test
+    public void testMakeRoomForEmergencyCallHasUnholdableCallAndManagedCallInHold() {
+        Call unholdableCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
+        when(unholdableCall.can(Connection.CAPABILITY_HOLD)).thenReturn(false);
+
+        Call managedHoldingCall = addSpyCall(SIM_1_HANDLE, CallState.ON_HOLD);
+        when(managedHoldingCall.isSelfManaged()).thenReturn(false);
+
+        Call newEmergencyCall = createSpyCall(SIM_1_HANDLE, CallState.NEW);
+        when(newEmergencyCall.isEmergencyCall()).thenReturn(true);
+
+        assertTrue(mCallsManager.makeRoomForOutgoingEmergencyCall(newEmergencyCall));
+        verify(unholdableCall).disconnect(anyString());
+    }
+
+    @SmallTest
+    @Test
+    public void testMakeRoomForEmergencyCallHasHoldableCall() {
+        Call holdableCall = addSpyCall(null, CallState.ACTIVE);
+        when(holdableCall.can(Connection.CAPABILITY_HOLD)).thenReturn(true);
+
+        Call newEmergencyCall = createSpyCall(SIM_1_HANDLE, CallState.NEW);
+        when(newEmergencyCall.isEmergencyCall()).thenReturn(true);
+
+        assertTrue(mCallsManager.makeRoomForOutgoingEmergencyCall(newEmergencyCall));
+        verify(holdableCall).hold(anyString());
+    }
+
+    @SmallTest
+    @Test
+    public void testMakeRoomForEmergencyCallHasUnholdableCall() {
+        Call unholdableCall = addSpyCall(null, CallState.ACTIVE);
+        when(unholdableCall.can(Connection.CAPABILITY_HOLD)).thenReturn(false);
+
+        Call newEmergencyCall = createSpyCall(SIM_1_HANDLE, CallState.NEW);
+        when(newEmergencyCall.isEmergencyCall()).thenReturn(true);
+
+        assertFalse(mCallsManager.makeRoomForOutgoingEmergencyCall(newEmergencyCall));
+    }
+
+    @SmallTest
+    @Test
+    public void testMakeRoomForOutgoingCallHasConnectingCall() {
+        Call ongoingCall = addSpyCall(SIM_2_HANDLE, CallState.CONNECTING);
+        Call newCall = createCall(SIM_1_HANDLE, CallState.NEW);
 
         assertTrue(mCallsManager.makeRoomForOutgoingCall(newCall));
         verify(ongoingCall).disconnect(anyLong(), anyString());
     }
 
+    @SmallTest
+    @Test
+    public void testMakeRoomForOutgoingCallForSameCall() {
+        addSpyCall(SIM_2_HANDLE, CallState.CONNECTING);
+        Call ongoingCall2 = addSpyCall();
+
+        assertTrue(mCallsManager.makeRoomForOutgoingCall(ongoingCall2));
+    }
+
+    @SmallTest
+    @Test
+    public void testMakeRoomForOutgoingCallHasOutgoingCallSelectingAccount() {
+        Call outgoingCall = addSpyCall(SIM_1_HANDLE, CallState.SELECT_PHONE_ACCOUNT);
+        Call newCall = createSpyCall(SIM_1_HANDLE, CallState.NEW);
+
+        assertTrue(mCallsManager.makeRoomForOutgoingCall(newCall));
+        verify(outgoingCall).disconnect(anyString());
+    }
+
+    @SmallTest
+    @Test
+    public void testMakeRoomForOutgoingCallHasDialingCall() {
+        addSpyCall(SIM_1_HANDLE, CallState.DIALING);
+        Call newCall = createSpyCall(SIM_1_HANDLE, CallState.NEW);
+
+        assertFalse(mCallsManager.makeRoomForOutgoingCall(newCall));
+    }
+
+    @MediumTest
+    @Test
+    public void testMakeRoomForOutgoingCallHasHoldableCall() {
+        Call holdableCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
+        when(holdableCall.can(Connection.CAPABILITY_HOLD)).thenReturn(true);
+
+        Call newCall = createSpyCall(CONNECTION_MGR_1_HANDLE, CallState.NEW);
+
+        assertTrue(mCallsManager.makeRoomForOutgoingCall(newCall));
+        verify(holdableCall).hold(anyString());
+    }
+
+    @SmallTest
+    @Test
+    public void testMakeRoomForOutgoingCallHasUnholdableCall() {
+        Call holdableCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
+        when(holdableCall.can(Connection.CAPABILITY_HOLD)).thenReturn(false);
+
+        Call newCall = createSpyCall(CONNECTION_MGR_1_HANDLE, CallState.NEW);
+
+        assertFalse(mCallsManager.makeRoomForOutgoingCall(newCall));
+    }
+
     /**
      * Verifies that changes to a {@link PhoneAccount}'s
      * {@link PhoneAccount#CAPABILITY_VIDEO_CALLING} capability will be reflected on a call.
@@ -1399,6 +1829,7 @@
         Call screenedCall = mock(Call.class);
         Bundle extra = new Bundle();
         when(screenedCall.getIntentExtras()).thenReturn(extra);
+        when(screenedCall.getTargetPhoneAccount()).thenReturn(SIM_1_HANDLE);
         String appName = "blah";
         CallFilteringResult result = new CallFilteringResult.Builder()
                 .setShouldAllowCall(true)
@@ -1477,7 +1908,7 @@
         when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
                 SIM_1_HANDLE);
         when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
-                any(), anyInt(), anyInt())).thenReturn(
+                any(), anyInt(), anyInt(), anyBoolean())).thenReturn(
                 new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
 
         // Let's add an existing call which is in connecting state; this emulates the case where
@@ -1604,7 +2035,35 @@
                 any(DisconnectCause.class));
         verify(callSpy, never()).setDisconnectCause(any(DisconnectCause.class));
     }
-    
+
+    @SmallTest
+    @Test
+    public void testCallStreamingStateChanged() throws Exception {
+        Call call = createCall(SIM_1_HANDLE, CallState.NEW);
+        call.setIsTransactionalCall(true);
+        CountDownLatch streamingStarted = new CountDownLatch(1);
+        CountDownLatch streamingStopped = new CountDownLatch(1);
+        Call.Listener l = new Call.ListenerBase() {
+            @Override
+            public void onCallStreamingStateChanged(Call call, boolean isStreaming) {
+                if (isStreaming) {
+                    streamingStarted.countDown();
+                } else {
+                    streamingStopped.countDown();
+                }
+            }
+        };
+        call.addListener(l);
+
+        // Start call streaming
+        call.startStreaming();
+        assertTrue(streamingStarted.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS));
+
+        // Stop call streaming
+        call.stopStreaming();
+        assertTrue(streamingStopped.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS));
+    }
+
     /**
      * Verifies that if call state goes from DIALING to DISCONNECTED, and a call diagnostic service
      * IS in use, it would call onCallDisconnected of the CallDiagnosticService
@@ -1645,6 +2104,79 @@
                 SELF_MANAGED_HANDLE.getUserHandle()));
     }
 
+    /**
+     * Emulate the case where a new incoming call is created but the connection fails for a known
+     * reason before being added to CallsManager. In this case, the listeners should be notified
+     * properly.
+     */
+    @Test
+    public void testIncomingCallCreatedButNotAddedNotifyListener() {
+        //The call is created and a listener is added:
+        Call incomingCall = createCall(SIM_2_HANDLE, null, CallState.NEW);
+        CallsManager.CallsManagerListener listener = mock(CallsManager.CallsManagerListener.class);
+        mCallsManager.addListener(listener);
+
+        //The connection fails before being added to CallsManager for a known reason:
+        incomingCall.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED));
+
+        //Ensure the listener is notified properly:
+        verify(listener).onCreateConnectionFailed(incomingCall);
+    }
+
+    /**
+     * Emulate the case where a new incoming call is created but the connection fails for a known
+     * reason after being added to CallsManager. Since the call was added to CallsManager, the
+     * listeners should not be notified via onCreateConnectionFailed().
+     */
+    @Test
+    public void testIncomingCallCreatedAndAddedDoNotNotifyListener() {
+        //The call is created and a listener is added:
+        Call incomingCall = createCall(SIM_2_HANDLE, null, CallState.NEW);
+        CallsManager.CallsManagerListener listener = mock(CallsManager.CallsManagerListener.class);
+        mCallsManager.addListener(listener);
+
+        //The call is added to CallsManager:
+        mCallsManager.addCall(incomingCall);
+
+        //The connection fails after being added to CallsManager for a known reason:
+        incomingCall.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED));
+
+        //Since the call was added to CallsManager, onCreateConnectionFailed shouldn't be invoked:
+        verify(listener, never()).onCreateConnectionFailed(incomingCall);
+    }
+
+    /**
+     * Emulate the case where a new outgoing call is created but is aborted before being added to
+     * CallsManager since there are no available phone accounts. In this case, the listeners
+     * should be notified properly.
+     */
+    @Test
+    public void testAbortOutgoingCallNoPhoneAccountsNotifyListeners() throws Exception {
+        // Setup a new outgoing call and add a listener
+        Call newCall = addSpyCall(CallState.NEW);
+        CallsManager.CallsManagerListener listener = mock(CallsManager.CallsManagerListener.class);
+        mCallsManager.addListener(listener);
+
+        // Ensure contact info lookup succeeds but do not set the phone account info
+        doAnswer(invocation -> {
+            Uri handle = invocation.getArgument(0);
+            CallerInfo info = new CallerInfo();
+            CompletableFuture<Pair<Uri, CallerInfo>> callerInfoFuture = new CompletableFuture<>();
+            callerInfoFuture.complete(new Pair<>(handle, info));
+            return callerInfoFuture;
+        }).when(mCallerInfoLookupHelper).startLookup(any(Uri.class));
+
+        // Start the outgoing call
+        CompletableFuture<Call> callFuture = mCallsManager.startOutgoingCall(
+                newCall.getHandle(), newCall.getTargetPhoneAccount(), new Bundle(),
+                UserHandle.CURRENT, new Intent(), "com.test.stuff");
+        Call result = callFuture.get(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
+
+        //Ensure the listener is notified properly:
+        verify(listener).onCreateConnectionFailed(any());
+        assertNull(result);
+    }
+
     @Test
     public void testIsInSelfManagedCallOnlySelfManaged() {
         Call selfManagedCall = createCall(SELF_MANAGED_HANDLE, CallState.ACTIVE);
@@ -1676,6 +2208,43 @@
                 new UserHandle(90210)));
     }
 
+    /**
+     * Verifies that if a {@link android.telecom.CallScreeningService} app can properly request
+     * notification show for rejected calls.
+     */
+    @SmallTest
+    @Test
+    public void testCallScreeningServiceRequestShowNotification() {
+        Call callSpy = addSpyCall(CallState.NEW);
+        CallFilteringResult result = new CallFilteringResult.Builder()
+                .setShouldAllowCall(false)
+                .setShouldReject(true)
+                .setCallScreeningComponentName("com.foo/.Blah")
+                .setCallScreeningAppName("Blah")
+                .setShouldAddToCallLog(true)
+                .setShouldShowNotification(true).build();
+
+        mCallsManager.onCallFilteringComplete(callSpy, result, false /* timeout */);
+        verify(mMissedCallNotifier).showMissedCallNotification(
+                any(MissedCallNotifier.CallInfo.class));
+    }
+
+    @Test
+    public void testSetStateOnlyCalledOnce() {
+        // GIVEN a new self-managed call
+        Call newCall = addSpyCall();
+        doReturn(true).when(newCall).isSelfManaged();
+        newCall.setState(CallState.DISCONNECTED, "");
+
+        // WHEN ActionSetCallState is given a disconnect call
+        assertEquals(CallState.DISCONNECTED, newCall.getState());
+        // attempt to set the call active
+        mCallsManager.createActionSetCallStateAndPerformAction(newCall, CallState.ACTIVE, "");
+
+        // THEN assert remains disconnected
+        assertEquals(CallState.DISCONNECTED, newCall.getState());
+    }
+
     @SmallTest
     @Test
     public void testCrossUserCallRedirectionEndEarlyForIncapablePhoneAccount() {
@@ -1693,6 +2262,613 @@
         assertTrue(argumentCaptor.getValue().contains("Unavailable phoneAccountHandle"));
     }
 
+    /**
+     * Verifies that target phone account is set in startOutgoingCall. The multi-user functionality
+     * is dependent on the call's phone account handle being present so this test ensures that
+     * existing outgoing call flow does not break from future updates.
+     * @throws Exception
+     */
+    @Test
+    public void testStartOutgoingCall_TargetPhoneAccountSet() throws Exception {
+        // Ensure contact info lookup succeeds
+        doAnswer(invocation -> {
+            Uri handle = invocation.getArgument(0);
+            CallerInfo info = new CallerInfo();
+            CompletableFuture<Pair<Uri, CallerInfo>> callerInfoFuture = new CompletableFuture<>();
+            callerInfoFuture.complete(new Pair<>(handle, info));
+            return callerInfoFuture;
+        }).when(mCallerInfoLookupHelper).startLookup(any(Uri.class));
+
+        // Ensure we have candidate phone account handle info.
+        when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
+                SIM_1_HANDLE);
+        when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
+                any(), anyInt(), anyInt(), anyBoolean())).thenReturn(
+                new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
+
+        // start an outgoing call
+        CompletableFuture<Call> callFuture = mCallsManager.startOutgoingCall(
+                TEST_ADDRESS, SIM_2_HANDLE, new Bundle(),
+                UserHandle.CURRENT, new Intent(), "com.test.stuff");
+        Call outgoingCall = callFuture.get();
+        // assert call was created
+        assertNotNull(outgoingCall);
+        // assert target phone account was set
+        assertNotNull(outgoingCall.getTargetPhoneAccount());
+    }
+
+    /**
+     * Verifies that target phone account is set before call filtering occurs.
+     * @throws Exception
+     */
+    @SmallTest
+    @Test
+    public void testOnSuccessfulIncomingCall_TargetPhoneAccountSet() throws Exception {
+        Call incomingCall = addSpyCall(CallState.NEW);
+        doReturn(false).when(incomingCall).can(Connection.CAPABILITY_HOLD);
+        doReturn(false).when(incomingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
+        doReturn(true).when(incomingCall).isSelfManaged();
+        doReturn(true).when(incomingCall).setState(anyInt(), any());
+        // assert phone account is present before onSuccessfulIncomingCall is called
+        assertNotNull(incomingCall.getTargetPhoneAccount());
+    }
+
+    /**
+     * Verifies that outgoing call's post call package name is set during
+     * onSuccessfulOutgoingCall.
+     * @throws Exception
+     */
+    @SmallTest
+    @Test
+    public void testPostCallPackageNameSetOnSuccessfulOutgoingCall() throws Exception {
+        Call outgoingCall = addSpyCall(CallState.NEW);
+        when(mCallsManager.getRoleManagerAdapter().getDefaultCallScreeningApp(
+                outgoingCall.getUserHandleFromTargetPhoneAccount()))
+                .thenReturn(DEFAULT_CALL_SCREENING_APP);
+        assertNull(outgoingCall.getPostCallPackageName());
+        mCallsManager.onSuccessfulOutgoingCall(outgoingCall, CallState.CONNECTING);
+        assertEquals(DEFAULT_CALL_SCREENING_APP, outgoingCall.getPostCallPackageName());
+    }
+
+    public class LatchedOutcomeReceiver implements OutcomeReceiver<Boolean,
+            CallException> {
+        CountDownLatch mCountDownLatch;
+        Boolean mIsOnResultExpected;
+
+        public LatchedOutcomeReceiver(CountDownLatch latch, boolean isOnResultExpected){
+            mCountDownLatch = latch;
+            mIsOnResultExpected = isOnResultExpected;
+        }
+
+        @Override
+        public void onResult(Boolean result) {
+            if(mIsOnResultExpected) {
+                mCountDownLatch.countDown();
+            }
+        }
+
+        @Override
+        public void onError(CallException error) {
+            OutcomeReceiver.super.onError(error);
+            if(!mIsOnResultExpected){
+                mCountDownLatch.countDown();
+            }
+        }
+    }
+
+    @SmallTest
+    @Test
+    public void testCanHold() {
+        Call newCall = addSpyCall();
+        when(newCall.isTransactionalCall()).thenReturn(true);
+        when(newCall.can(Connection.CAPABILITY_SUPPORT_HOLD)).thenReturn(false);
+        assertFalse(mCallsManager.canHold(newCall));
+        when(newCall.can(Connection.CAPABILITY_SUPPORT_HOLD)).thenReturn(true);
+        assertTrue(mCallsManager.canHold(newCall));
+    }
+
+    @MediumTest
+    @Test
+    public void testOnFailedOutgoingCallRemovesCallImmediately() {
+        Call call = addSpyCall();
+        when(call.isDisconnectHandledViaFuture()).thenReturn(false);
+        CompletableFuture future = CompletableFuture.completedFuture(true);
+        when(mInCallController.getBindingFuture()).thenReturn(future);
+
+        mCallsManager.onFailedOutgoingCall(call, new DisconnectCause(DisconnectCause.OTHER));
+
+        future.join();
+        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+
+        assertFalse(mCallsManager.getCalls().contains(call));
+    }
+
+    @MediumTest
+    @Test
+    public void testHoldTransactional() throws Exception {
+        CountDownLatch latch = new CountDownLatch(1);
+        Call newCall = addSpyCall();
+
+        // case 1: no active call, no need to put the call on hold
+        when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(null);
+        mCallsManager.transactionHoldPotentialActiveCallForNewCall(newCall,
+                new LatchedOutcomeReceiver(latch, true));
+        waitForCountDownLatch(latch);
+
+        // case 2: active call == new call, no need to put the call on hold
+        latch = new CountDownLatch(1);
+        when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(newCall);
+        mCallsManager.transactionHoldPotentialActiveCallForNewCall(newCall,
+                new LatchedOutcomeReceiver(latch, true));
+        waitForCountDownLatch(latch);
+
+        // case 3: cannot hold current active call early check
+        Call cannotHoldCall = addSpyCall(SIM_1_HANDLE, null,
+                CallState.ACTIVE, 0, 0);
+        latch = new CountDownLatch(1);
+        when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(cannotHoldCall);
+        mCallsManager.transactionHoldPotentialActiveCallForNewCall(newCall,
+                new LatchedOutcomeReceiver(latch, false));
+        waitForCountDownLatch(latch);
+
+        // case 4: activeCall != newCall && canHold(activeCall)
+        Call canHoldCall = addSpyCall(SIM_1_HANDLE, null,
+                CallState.ACTIVE, Connection.CAPABILITY_HOLD, 0);
+        latch = new CountDownLatch(1);
+        when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(canHoldCall);
+        mCallsManager.transactionHoldPotentialActiveCallForNewCall(newCall,
+                new LatchedOutcomeReceiver(latch, true));
+        waitForCountDownLatch(latch);
+    }
+
+    @SmallTest
+    @Test
+    public void testGetNumCallsWithState_MultiUser() throws Exception {
+        when(mContext.checkCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+        // Add call under secondary user
+        Call call = addSpyCall(SIM_1_HANDLE_SECONDARY, CallState.ACTIVE);
+        when(call.getPhoneAccountFromHandle()).thenReturn(SIM_1_ACCOUNT_SECONDARY);
+        // Verify that call is visible to primary user
+        assertEquals(mCallsManager.getNumCallsWithState(0, null,
+                UserHandle.CURRENT_OR_SELF, true,
+                null, CallState.ACTIVE), 1);
+        // Verify that call is not visible to primary user
+        // when a different phone account handle is specified.
+        assertEquals(mCallsManager.getNumCallsWithState(0, null,
+                UserHandle.CURRENT_OR_SELF, true,
+                SIM_1_HANDLE, CallState.ACTIVE), 0);
+        // Deny INTERACT_ACROSS_USERS permission and verify that call is not visible to primary user
+        assertEquals(mCallsManager.getNumCallsWithState(0, null,
+                UserHandle.CURRENT_OR_SELF, false,
+                null, CallState.ACTIVE), 0);
+    }
+
+    public void waitForCountDownLatch(CountDownLatch latch) throws InterruptedException {
+            boolean success = latch.await(5000, TimeUnit.MILLISECONDS);
+            if (!success) {
+                fail("assertOnResultWasReceived success failed");
+            }
+    }
+
+    /**
+     * When queryCurrentLocation is called, check whether the result is received through the
+     * ResultReceiver.
+     * @throws Exception if {@link CompletableFuture#get()} fails.
+     */
+    @Test
+    public void testQueryCurrentLocationCheckOnReceiveResult() throws Exception {
+        ConnectionServiceWrapper service = new ConnectionServiceWrapper(
+                new ComponentName(mContext.getPackageName(),
+                        mContext.getPackageName().getClass().getName()),
+                null, mPhoneAccountRegistrar, mCallsManager, mContext, mLock, null);
+
+        CompletableFuture<String> resultFuture = new CompletableFuture<>();
+        try {
+            service.queryCurrentLocation(500L, "Test_provider",
+                    new ResultReceiver(new Handler(Looper.getMainLooper())) {
+                        @Override
+                        protected void onReceiveResult(int resultCode, Bundle result) {
+                            super.onReceiveResult(resultCode, result);
+                            resultFuture.complete("onReceiveResult");
+                        }
+                    });
+        } catch (Exception e) {
+            resultFuture.complete("Exception : " + e);
+        }
+
+        String result = resultFuture.get(1000L, TimeUnit.MILLISECONDS);
+        assertTrue(result.contains("onReceiveResult"));
+    }
+
+    @SmallTest
+    @Test
+    public void testOnFailedOutgoingCallUnholdsCallAfterLocallyDisconnect() {
+        Call existingCall = addSpyCall();
+        when(existingCall.getState()).thenReturn(CallState.ON_HOLD);
+
+        Call call = addSpyCall();
+        when(call.isDisconnectHandledViaFuture()).thenReturn(false);
+        when(call.isDisconnectingChildCall()).thenReturn(false);
+        CompletableFuture future = CompletableFuture.completedFuture(true);
+        when(mInCallController.getBindingFuture()).thenReturn(future);
+
+        mCallsManager.disconnectCall(call);
+        mCallsManager.onFailedOutgoingCall(call, new DisconnectCause(DisconnectCause.OTHER));
+
+        future.join();
+        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+
+        verify(existingCall).unhold();
+    }
+
+    @MediumTest
+    @Test
+    public void testOnFailedOutgoingCallUnholdsCallIfNoHoldButton() {
+        Call existingCall = addSpyCall();
+        when(existingCall.can(Connection.CAPABILITY_SUPPORT_HOLD)).thenReturn(false);
+        when(existingCall.getState()).thenReturn(CallState.ON_HOLD);
+
+        Call call = addSpyCall();
+        when(call.isDisconnectHandledViaFuture()).thenReturn(false);
+        CompletableFuture future = CompletableFuture.completedFuture(true);
+        when(mInCallController.getBindingFuture()).thenReturn(future);
+
+        mCallsManager.disconnectCall(call);
+        mCallsManager.onFailedOutgoingCall(call, new DisconnectCause(DisconnectCause.OTHER));
+
+        future.join();
+        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+
+        verify(existingCall).unhold();
+    }
+
+    @MediumTest
+    @Test
+    public void testOnCallFilteringCompleteRemovesUnwantedCallComposerAttachments() {
+        Call call = addSpyCall(CallState.NEW);
+        Bundle extras = mock(Bundle.class);
+        when(call.getIntentExtras()).thenReturn(extras);
+
+        final int attachmentDisabledMask = ~0
+                ^ CallScreeningService.CallResponse.CALL_COMPOSER_ATTACHMENT_LOCATION
+                ^ CallScreeningService.CallResponse.CALL_COMPOSER_ATTACHMENT_SUBJECT
+                ^ CallScreeningService.CallResponse.CALL_COMPOSER_ATTACHMENT_PRIORITY;
+        CallScreeningService.ParcelableCallResponse response =
+                mock(CallScreeningService.ParcelableCallResponse.class);
+        when(response.getCallComposerAttachmentsToShow()).thenReturn(attachmentDisabledMask);
+
+        CallFilteringResult result = new CallFilteringResult.Builder()
+                .setCallScreeningResponse(response, true)
+                .build();
+
+        mCallsManager.onCallFilteringComplete(call, result, false);
+
+        verify(extras).remove(TelecomManager.EXTRA_LOCATION);
+        verify(extras).remove(TelecomManager.EXTRA_CALL_SUBJECT);
+        verify(extras).remove(TelecomManager.EXTRA_PRIORITY);
+    }
+
+    @SmallTest
+    @Test
+    public void testOnFailedIncomingCall() {
+        Call call = createSpyCall(SIM_1_HANDLE, CallState.NEW);
+
+        mCallsManager.onFailedIncomingCall(call);
+
+        assertEquals(CallState.DISCONNECTED, call.getState());
+        verify(call).removeListener(mCallsManager);
+    }
+
+    @SmallTest
+    @Test
+    public void testOnSuccessfulUnknownCall() {
+        Call call = createSpyCall(SIM_1_HANDLE, CallState.NEW);
+
+        final int newState = CallState.ACTIVE;
+        mCallsManager.onSuccessfulUnknownCall(call, newState);
+
+        assertEquals(newState, call.getState());
+        assertTrue(mCallsManager.getCalls().contains(call));
+    }
+
+    @SmallTest
+    @Test
+    public void testOnFailedUnknownCall() {
+        Call call = createSpyCall(SIM_1_HANDLE, CallState.NEW);
+
+        mCallsManager.onFailedUnknownCall(call);
+
+        assertEquals(CallState.DISCONNECTED, call.getState());
+        verify(call).removeListener(mCallsManager);
+    }
+
+    @SmallTest
+    @Test
+    public void testOnRingbackRequested() {
+        Call call = mock(Call.class);
+        final boolean ringback = true;
+
+        CallsManager.CallsManagerListener listener = mock(CallsManager.CallsManagerListener.class);
+        mCallsManager.addListener(listener);
+
+        mCallsManager.onRingbackRequested(call, ringback);
+
+        verify(listener).onRingbackRequested(call, ringback);
+    }
+
+    @MediumTest
+    @Test
+    public void testSetCallDialingAndDontIncreaseVolume() {
+        // Start with a non zero volume.
+        mComponentContextFixture.getAudioManager().setStreamVolume(AudioManager.STREAM_VOICE_CALL,
+                4, 0 /* flags */);
+
+        Call call = mock(Call.class);
+        mCallsManager.markCallAsDialing(call);
+
+        // We set the volume to non-zero above, so expect 1
+        verify(mComponentContextFixture.getAudioManager(), times(1)).setStreamVolume(
+                eq(AudioManager.STREAM_VOICE_CALL), anyInt(), anyInt());
+    }
+    @MediumTest
+    @Test
+    public void testSetCallDialingAndIncreaseVolume() {
+        // Start with a zero volume stream.
+        mComponentContextFixture.getAudioManager().setStreamVolume(AudioManager.STREAM_VOICE_CALL,
+                0, 0 /* flags */);
+
+        Call call = mock(Call.class);
+        mCallsManager.markCallAsDialing(call);
+
+        // We set the volume to zero above, so expect 2
+        verify(mComponentContextFixture.getAudioManager(), times(2)).setStreamVolume(
+                eq(AudioManager.STREAM_VOICE_CALL), anyInt(), anyInt());
+    }
+
+    @MediumTest
+    @Test
+    public void testSetCallActiveAndDontIncreaseVolume() {
+        // Start with a non-zero volume.
+        mComponentContextFixture.getAudioManager().setStreamVolume(AudioManager.STREAM_VOICE_CALL,
+                4, 0 /* flags */);
+
+        Call call = mock(Call.class);
+        mCallsManager.markCallAsActive(call);
+
+        // We set the volume to non-zero above, so expect 1 only.
+        verify(mComponentContextFixture.getAudioManager(), times(1)).setStreamVolume(
+                eq(AudioManager.STREAM_VOICE_CALL), anyInt(), anyInt());
+    }
+
+    @MediumTest
+    @Test
+    public void testHandoverToIsAccepted() {
+        Call sourceCall = addSpyCall(CONNECTION_MGR_1_HANDLE, CallState.NEW);
+        Call call = addSpyCall(CONNECTION_MGR_1_HANDLE, CallState.NEW);
+        when(call.getHandoverSourceCall()).thenReturn(sourceCall);
+        when(call.getHandoverState()).thenReturn(HandoverState.HANDOVER_TO_STARTED);
+
+        mCallsManager.createActionSetCallStateAndPerformAction(call, CallState.ACTIVE, "");
+
+        verify(call).setHandoverState(HandoverState.HANDOVER_ACCEPTED);
+        verify(call).onHandoverComplete();
+        verify(sourceCall).setHandoverState(HandoverState.HANDOVER_ACCEPTED);
+        verify(sourceCall).onHandoverComplete();
+        verify(sourceCall).disconnect();
+    }
+
+    @MediumTest
+    @Test
+    public void testSelfManagedHandoverToIsAccepted() {
+        Call sourceCall = addSpyCall(CONNECTION_MGR_1_HANDLE, CallState.NEW);
+        Call call = addSpyCall(SELF_MANAGED_HANDLE, CallState.NEW);
+        when(call.getHandoverSourceCall()).thenReturn(sourceCall);
+        when(call.getHandoverState()).thenReturn(HandoverState.HANDOVER_TO_STARTED);
+        when(call.isSelfManaged()).thenReturn(true);
+        Call otherCall = addSpyCall(CONNECTION_MGR_1_HANDLE, CallState.ON_HOLD);
+
+        mCallsManager.createActionSetCallStateAndPerformAction(call, CallState.ACTIVE, "");
+
+        verify(call).setHandoverState(HandoverState.HANDOVER_ACCEPTED);
+        verify(call).onHandoverComplete();
+        verify(sourceCall).setHandoverState(HandoverState.HANDOVER_ACCEPTED);
+        verify(sourceCall).onHandoverComplete();
+        verify(sourceCall, times(2)).disconnect();
+        verify(otherCall).disconnect();
+    }
+
+    @SmallTest
+    @Test
+    public void testHandoverToIsRejected() {
+        Call sourceCall = addSpyCall(CONNECTION_MGR_1_HANDLE, CallState.NEW);
+        Call call = addSpyCall(CONNECTION_MGR_1_HANDLE, CallState.NEW);
+        when(call.getHandoverSourceCall()).thenReturn(sourceCall);
+        when(call.getHandoverState()).thenReturn(HandoverState.HANDOVER_TO_STARTED);
+        when(call.getConnectionService()).thenReturn(mock(ConnectionServiceWrapper.class));
+
+        mCallsManager.createActionSetCallStateAndPerformAction(
+                call, CallState.DISCONNECTED, "");
+
+        verify(sourceCall).onConnectionEvent(eq(Connection.EVENT_HANDOVER_FAILED), any());
+        verify(sourceCall).onHandoverFailed(
+                    android.telecom.Call.Callback.HANDOVER_FAILURE_USER_REJECTED);
+
+        verify(call).sendCallEvent(eq(android.telecom.Call.EVENT_HANDOVER_FAILED), any());
+        verify(call).markFinishedHandoverStateAndCleanup(HandoverState.HANDOVER_FAILED);
+    }
+
+    @SmallTest
+    @Test
+    public void testHandoverFromIsStarted() {
+        Call destinationCall = addSpyCall(CONNECTION_MGR_1_HANDLE, CallState.NEW);
+        Call call = addSpyCall(CONNECTION_MGR_1_HANDLE, CallState.NEW);
+        when(call.getHandoverDestinationCall()).thenReturn(destinationCall);
+        when(call.getHandoverState()).thenReturn(HandoverState.HANDOVER_FROM_STARTED);
+
+        mCallsManager.createActionSetCallStateAndPerformAction(
+                call, CallState.DISCONNECTED, "");
+
+        verify(destinationCall).sendCallEvent(
+                eq(android.telecom.Call.EVENT_HANDOVER_SOURCE_DISCONNECTED), any());
+    }
+
+    @SmallTest
+    @Test
+    public void testHandoverFromIsAccepted() {
+        Call destinationCall = addSpyCall(CONNECTION_MGR_1_HANDLE, CallState.NEW);
+        Call call = addSpyCall(CONNECTION_MGR_1_HANDLE, CallState.NEW);
+        when(call.getHandoverDestinationCall()).thenReturn(destinationCall);
+        when(call.getHandoverState()).thenReturn(HandoverState.HANDOVER_ACCEPTED);
+
+        mCallsManager.createActionSetCallStateAndPerformAction(
+                call, CallState.DISCONNECTED, "");
+
+        verify(call).onConnectionEvent(eq(Connection.EVENT_HANDOVER_COMPLETE), any());
+        verify(call).onHandoverComplete();
+        verify(call).markFinishedHandoverStateAndCleanup(HandoverState.HANDOVER_COMPLETE);
+        verify(destinationCall).sendCallEvent(
+                eq(android.telecom.Call.EVENT_HANDOVER_COMPLETE), any());
+        verify(destinationCall).onHandoverComplete();
+    }
+
+    @SmallTest
+    @Test
+    public void testSelfManagedHandoverFromIsAccepted() {
+        Call destinationCall = addSpyCall(SELF_MANAGED_HANDLE, CallState.NEW);
+        when(destinationCall.isSelfManaged()).thenReturn(true);
+        Call call = addSpyCall(CONNECTION_MGR_1_HANDLE, CallState.NEW);
+        when(call.getHandoverDestinationCall()).thenReturn(destinationCall);
+        when(call.getHandoverState()).thenReturn(HandoverState.HANDOVER_ACCEPTED);
+        Call otherCall = addSpyCall(CONNECTION_MGR_1_HANDLE, CallState.ON_HOLD);
+
+        mCallsManager.createActionSetCallStateAndPerformAction(
+                call, CallState.DISCONNECTED, "");
+
+        verify(call).onConnectionEvent(eq(Connection.EVENT_HANDOVER_COMPLETE), any());
+        verify(call).onHandoverComplete();
+        verify(call).markFinishedHandoverStateAndCleanup(HandoverState.HANDOVER_COMPLETE);
+        verify(destinationCall).sendCallEvent(
+                eq(android.telecom.Call.EVENT_HANDOVER_COMPLETE), any());
+        verify(destinationCall).onHandoverComplete();
+        verify(otherCall).disconnect();
+    }
+
+    @MediumTest
+    @Test
+    public void testGetNumUnholdableCallsForOtherConnectionService() {
+        final int notDialingState = CallState.ACTIVE;
+        final PhoneAccountHandle accountHande = SIM_1_HANDLE;
+        assertFalse(mCallsManager.hasUnholdableCallsForOtherConnectionService(accountHande));
+
+        Call unholdableCall = addSpyCall(accountHande, notDialingState);
+        when(unholdableCall.can(Connection.CAPABILITY_HOLD)).thenReturn(false);
+        assertFalse(mCallsManager.hasUnholdableCallsForOtherConnectionService(accountHande));
+
+        Call holdableCall = addSpyCall(accountHande, notDialingState);
+        when(holdableCall.can(Connection.CAPABILITY_HOLD)).thenReturn(true);
+        assertFalse(mCallsManager.hasUnholdableCallsForOtherConnectionService(accountHande));
+
+        Call dialingCall = addSpyCall(accountHande, CallState.DIALING);
+        when(dialingCall.can(Connection.CAPABILITY_HOLD)).thenReturn(true);
+        assertFalse(mCallsManager.hasUnholdableCallsForOtherConnectionService(accountHande));
+
+        Call externalCall = addSpyCall(accountHande, notDialingState);
+        when(externalCall.isExternalCall()).thenReturn(true);
+        assertFalse(mCallsManager.hasUnholdableCallsForOtherConnectionService(accountHande));
+
+        Call unholdableOtherCall = addSpyCall(VOIP_1_HANDLE, notDialingState);
+        when(unholdableOtherCall.can(Connection.CAPABILITY_HOLD)).thenReturn(false);
+        assertTrue(mCallsManager.hasUnholdableCallsForOtherConnectionService(accountHande));
+        assertEquals(1, mCallsManager.getNumUnholdableCallsForOtherConnectionService(accountHande));
+    }
+
+    @SmallTest
+    @Test
+    public void testHasManagedCalls() {
+        assertFalse(mCallsManager.hasManagedCalls());
+
+        Call selfManagedCall = addSpyCall();
+        when(selfManagedCall.isSelfManaged()).thenReturn(true);
+        assertFalse(mCallsManager.hasManagedCalls());
+
+        Call externalCall = addSpyCall();
+        when(externalCall.isSelfManaged()).thenReturn(false);
+        when(externalCall.isExternalCall()).thenReturn(true);
+        assertFalse(mCallsManager.hasManagedCalls());
+
+        Call managedCall = addSpyCall();
+        when(managedCall.isSelfManaged()).thenReturn(false);
+        assertTrue(mCallsManager.hasManagedCalls());
+    }
+
+    @SmallTest
+    @Test
+    public void testHasSelfManagedCalls() {
+        Call managedCall = addSpyCall();
+        when(managedCall.isSelfManaged()).thenReturn(false);
+        assertFalse(mCallsManager.hasSelfManagedCalls());
+
+        Call selfManagedCall = addSpyCall();
+        when(selfManagedCall.isSelfManaged()).thenReturn(true);
+        assertTrue(mCallsManager.hasSelfManagedCalls());
+    }
+
+    /**
+     * Verifies when {@link CallsManager} receives a carrier config change it will trigger an
+     * update of the emergency call notification.
+     * Note: this test mocks out {@link BlockedNumbersAdapter} so does not actually test posting of
+     * the notification.  Notification posting in the actual implementation is covered by
+     * {@link BlockedNumbersUtilTests}.
+     */
+    @SmallTest
+    @Test
+    public void testUpdateEmergencyCallNotificationOnCarrierConfigChange() {
+        when(mBlockedNumbersAdapter.shouldShowEmergencyCallNotification(any(Context.class)))
+                .thenReturn(true);
+        mComponentContextFixture.getBroadcastReceivers().forEach(c -> c.onReceive(mContext,
+                new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)));
+        verify(mBlockedNumbersAdapter).updateEmergencyCallNotification(any(Context.class),
+                eq(true));
+
+        when(mBlockedNumbersAdapter.shouldShowEmergencyCallNotification(any(Context.class)))
+                .thenReturn(false);
+        mComponentContextFixture.getBroadcastReceivers().forEach(c -> c.onReceive(mContext,
+                new Intent(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)));
+        verify(mBlockedNumbersAdapter).updateEmergencyCallNotification(any(Context.class),
+                eq(false));
+    }
+
+    /**
+     * Verifies when {@link CallsManager} receives a signal from the blocked number provider that
+     * the call blocking enabled state changes, it will trigger an update of the emergency call
+     * notification.
+     * Note: this test mocks out {@link BlockedNumbersAdapter} so does not actually test posting of
+     * the notification.  Notification posting in the actual implementation is covered by
+     * {@link BlockedNumbersUtilTests}.
+     */
+    @SmallTest
+    @Test
+    public void testUpdateEmergencyCallNotificationOnNotificationVisibilityChange() {
+        when(mBlockedNumbersAdapter.shouldShowEmergencyCallNotification(any(Context.class)))
+                .thenReturn(true);
+        mComponentContextFixture.getBroadcastReceivers().forEach(c -> c.onReceive(mContext,
+                new Intent(
+                        BlockedNumberContract.SystemContract
+                                .ACTION_BLOCK_SUPPRESSION_STATE_CHANGED)));
+        verify(mBlockedNumbersAdapter).updateEmergencyCallNotification(any(Context.class),
+                eq(true));
+
+        when(mBlockedNumbersAdapter.shouldShowEmergencyCallNotification(any(Context.class)))
+                .thenReturn(false);
+        mComponentContextFixture.getBroadcastReceivers().forEach(c -> c.onReceive(mContext,
+                new Intent(
+                        BlockedNumberContract.SystemContract
+                                .ACTION_BLOCK_SUPPRESSION_STATE_CHANGED)));
+        verify(mBlockedNumbersAdapter).updateEmergencyCallNotification(any(Context.class),
+                eq(false));
+    }
+
     private Call addSpyCall() {
         return addSpyCall(SIM_2_HANDLE, CallState.ACTIVE);
     }
@@ -1780,9 +2956,15 @@
         TelephonyManager mockTelephonyManager = mComponentContextFixture.getTelephonyManager();
         when(mockTelephonyManager.getMaxNumberOfSimultaneouslyActiveSims()).thenReturn(1);
         when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
-                any(), anyInt(), anyInt())).thenReturn(
+                any(), anyInt(), anyInt(), anyBoolean())).thenReturn(
                 new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
         when(mPhoneAccountRegistrar.getSimPhoneAccountsOfCurrentUser()).thenReturn(
                 new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
     }
+
+    private void setMaxActiveVoiceSubscriptions(int num) {
+        TelephonyManager mockTelephonyManager = mComponentContextFixture.getTelephonyManager();
+        when(mockTelephonyManager.getPhoneCapability()).thenReturn(mPhoneCapability);
+        when(mPhoneCapability.getMaxActiveVoiceSubscriptions()).thenReturn(num);
+    }
 }
diff --git a/tests/src/com/android/server/telecom/tests/CarModeTrackerTest.java b/tests/src/com/android/server/telecom/tests/CarModeTrackerTest.java
index 4ad46ae..6056747 100644
--- a/tests/src/com/android/server/telecom/tests/CarModeTrackerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CarModeTrackerTest.java
@@ -236,6 +236,28 @@
     }
 
     /**
+     * Verifies that setting automotive projection overrides entering car mode with the highest
+     * priority of 0. Also ensures exiting car mode doesn't interfere with the automotive
+     * projection being set.
+     */
+    @Test
+    public void testInterleaveCarModeAndProjectionMode() {
+        mCarModeTracker.handleEnterCarMode(0, CAR_MODE_APP1_PACKAGE_NAME);
+        assertEquals(CAR_MODE_APP1_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage());
+        assertTrue(mCarModeTracker.isInCarMode());
+
+        mCarModeTracker.handleSetAutomotiveProjection(CAR_MODE_APP2_PACKAGE_NAME);
+        assertEquals(CAR_MODE_APP2_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage());
+        assertTrue(mCarModeTracker.isInCarMode());
+
+        mCarModeTracker.handleExitCarMode(0, CAR_MODE_APP1_PACKAGE_NAME);
+        assertEquals(CAR_MODE_APP2_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage());
+        assertTrue(mCarModeTracker.isInCarMode());
+
+        mCarModeTracker.handleReleaseAutomotiveProjection();
+    }
+
+    /**
      * Verifies that if we set automotive projection more than once with the same package, nothing
      * changes.
      */
diff --git a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
index 477ca9f..cc22de2 100644
--- a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
@@ -52,11 +52,16 @@
 import android.content.res.Resources;
 import android.hardware.SensorPrivacyManager;
 import android.location.CountryDetector;
+import android.location.LocationManager;
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
+import android.os.BugreportManager;
 import android.os.Bundle;
+import android.os.DropBoxManager;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IInterface;
+import android.os.Looper;
 import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.UserHandle;
@@ -74,6 +79,7 @@
 import android.telephony.TelephonyRegistryManager;
 import android.test.mock.MockContext;
 import android.util.DisplayMetrics;
+import android.view.accessibility.AccessibilityManager;
 
 import java.io.File;
 import java.io.IOException;
@@ -85,6 +91,8 @@
 import java.util.Map;
 import java.util.concurrent.Executor;
 
+import static android.content.Context.DEVICE_ID_DEFAULT;
+
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.matches;
 import static org.mockito.ArgumentMatchers.nullable;
@@ -106,6 +114,7 @@
  * property points to an application context implementing all the nontrivial functionality.
  */
 public class ComponentContextFixture implements TestFixture<Context> {
+    private HandlerThread mHandlerThread;
 
     public class FakeApplicationContext extends MockContext {
         @Override
@@ -126,6 +135,9 @@
         }
 
         @Override
+        public Context createAttributionContext(String attributionTag) { return this; }
+
+        @Override
         public String getPackageName() {
             return "com.android.server.telecom.tests";
         }
@@ -199,6 +211,8 @@
                     return mAudioManager;
                 case Context.TELEPHONY_SERVICE:
                     return mTelephonyManager;
+                case Context.LOCATION_SERVICE:
+                    return mLocationManager;
                 case Context.APP_OPS_SERVICE:
                     return mAppOpsManager;
                 case Context.NOTIFICATION_SERVICE:
@@ -229,6 +243,8 @@
                     return mPermissionCheckerManager;
                 case Context.SENSOR_PRIVACY_SERVICE:
                     return mSensorPrivacyManager;
+                case Context.ACCESSIBILITY_SERVICE:
+                    return mAccessibilityManager;
                 default:
                     return null;
             }
@@ -262,8 +278,14 @@
                 return Context.SENSOR_PRIVACY_SERVICE;
             } else if (svcClass == NotificationManager.class) {
                 return Context.NOTIFICATION_SERVICE;
+            } else if (svcClass == AccessibilityManager.class) {
+                return Context.ACCESSIBILITY_SERVICE;
+            } else if (svcClass == DropBoxManager.class) {
+                return Context.DROPBOX_SERVICE;
+            } else if (svcClass == BugreportManager.class) {
+                return Context.BUGREPORT_SERVICE;
             }
-            throw new UnsupportedOperationException();
+            throw new UnsupportedOperationException(svcClass.getName());
         }
 
         @Override
@@ -292,6 +314,15 @@
         }
 
         @Override
+        public Looper getMainLooper() {
+            if (mHandlerThread == null) {
+                mHandlerThread = new HandlerThread(this.getClass().getSimpleName());
+                mHandlerThread.start();
+            }
+            return mHandlerThread.getLooper();
+        }
+
+        @Override
         public ContentResolver getContentResolver() {
             return new ContentResolver(mApplicationContextSpy) {
                 @Override
@@ -331,30 +362,34 @@
 
         @Override
         public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
-            // TODO -- this is called by WiredHeadsetManager!!!
+            mBroadcastReceivers.add(receiver);
             return null;
         }
 
         @Override
         public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags) {
+            mBroadcastReceivers.add(receiver);
             return null;
         }
 
         @Override
         public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
                 String broadcastPermission, Handler scheduler) {
+            mBroadcastReceivers.add(receiver);
             return null;
         }
 
         @Override
         public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
                 String broadcastPermission, Handler scheduler, int flags) {
+            mBroadcastReceivers.add(receiver);
             return null;
         }
 
         @Override
         public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle handle,
                 IntentFilter filter, String broadcastPermission, Handler scheduler) {
+            mBroadcastReceivers.add(receiver);
             return null;
         }
 
@@ -534,6 +569,11 @@
         public Resources getResources() {
             return mResources;
         }
+
+        @Override
+        public int getDeviceId() {
+          return DEVICE_ID_DEFAULT;
+        }
     };
 
     // The application context is the most important object this class provides to the system
@@ -551,8 +591,10 @@
     private final Executor mMainExecutor = mock(Executor.class);
     private final AudioManager mAudioManager = spy(new FakeAudioManager(mContext));
     private final TelephonyManager mTelephonyManager = mock(TelephonyManager.class);
+    private final LocationManager mLocationManager = mock(LocationManager.class);
     private final AppOpsManager mAppOpsManager = mock(AppOpsManager.class);
     private final NotificationManager mNotificationManager = mock(NotificationManager.class);
+    private final AccessibilityManager mAccessibilityManager = mock(AccessibilityManager.class);
     private final UserManager mUserManager = mock(UserManager.class);
     private final StatusBarManager mStatusBarManager = mock(StatusBarManager.class);
     private SubscriptionManager mSubscriptionManager = mock(SubscriptionManager.class);
@@ -571,6 +613,7 @@
             mock(PermissionCheckerManager.class);
     private final PermissionInfo mPermissionInfo = mock(PermissionInfo.class);
     private final SensorPrivacyManager mSensorPrivacyManager = mock(SensorPrivacyManager.class);
+    private final List<BroadcastReceiver> mBroadcastReceivers = new ArrayList<>();
 
     private TelecomManager mTelecomManager = mock(TelecomManager.class);
 
@@ -763,6 +806,10 @@
         return mTelephonyManager;
     }
 
+    public AudioManager getAudioManager() {
+        return mAudioManager;
+    }
+
     public CarrierConfigManager getCarrierConfigManager() {
         return mCarrierConfigManager;
     }
@@ -771,6 +818,10 @@
         return mNotificationManager;
     }
 
+    public List<BroadcastReceiver> getBroadcastReceivers() {
+        return mBroadcastReceivers;
+    }
+
     private void addService(String action, ComponentName name, IInterface service) {
         mComponentNamesByAction.put(action, name);
         mServiceByComponentName.put(name, service);
diff --git a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
index 6e6646f..c8da78c 100755
--- a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
@@ -34,6 +34,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.telecom.CallAudioState;
+import android.telecom.CallEndpoint;
 import android.telecom.CallScreeningService;
 import android.telecom.Conference;
 import android.telecom.Connection;
@@ -343,6 +344,18 @@
                 throws RemoteException { }
 
         @Override
+        public void onCallEndpointChanged(String callId, CallEndpoint callEndpoint,
+                Session.Info sessionInfo) { }
+
+        @Override
+        public void onAvailableCallEndpointsChanged(String callId,
+                List<CallEndpoint> availableCallEndpoints, Session.Info sessionInfo) { }
+
+        @Override
+        public void onMuteStateChanged(String callId, boolean isMuted,
+                Session.Info sessionInfo) { }
+
+        @Override
         public void onUsingAlternativeUi(String activeCallId, boolean usingAlternativeUi,
                 Session.Info info) throws RemoteException { }
 
diff --git a/tests/src/com/android/server/telecom/tests/CreateConnectionProcessorTest.java b/tests/src/com/android/server/telecom/tests/CreateConnectionProcessorTest.java
index cb376af..f977ddb 100644
--- a/tests/src/com/android/server/telecom/tests/CreateConnectionProcessorTest.java
+++ b/tests/src/com/android/server/telecom/tests/CreateConnectionProcessorTest.java
@@ -16,8 +16,10 @@
 
 package com.android.server.telecom.tests;
 
+import android.Manifest;
 import android.content.ComponentName;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Binder;
@@ -55,6 +57,8 @@
 import java.util.UUID;
 
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyList;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Matchers.any;
@@ -75,6 +79,7 @@
     private static final String TEST_PACKAGE = "com.android.server.telecom.tests";
     private static final String TEST_CLASS =
             "com.android.server.telecom.tests.MockConnectionService";
+    private static final UserHandle USER_HANDLE_10 = new UserHandle(10);
 
     @Mock
     ConnectionServiceRepository mMockConnectionServiceRepository;
@@ -137,7 +142,10 @@
                                 SubscriptionManager.INVALID_SIM_SLOT_INDEX);
                     }
                 });
-        when(mMockAccountRegistrar.getAllPhoneAccountsOfCurrentUser()).thenReturn(phoneAccounts);
+        when(mMockAccountRegistrar.getAllPhoneAccounts(any(UserHandle.class), anyBoolean()))
+                .thenReturn(phoneAccounts);
+        when(mMockCall.getUserHandleFromTargetPhoneAccount()).
+                thenReturn(Binder.getCallingUserHandle());
     }
 
     @Override
@@ -200,7 +208,7 @@
         ConnectionServiceWrapper service = makeConnectionServiceWrapper();
         // Make sure the target phone account has the correct permissions
         PhoneAccount mFakeTargetPhoneAccount = makeQuickAccount("cm_acct",
-                PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION);
+                PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION, null);
         when(mMockAccountRegistrar.getPhoneAccountUnchecked(pAHandle)).thenReturn(
                 mFakeTargetPhoneAccount);
 
@@ -229,7 +237,7 @@
         ConnectionServiceWrapper service = makeConnectionServiceWrapper();
         when(mMockCall.getConnectionManagerPhoneAccount()).thenReturn(callManagerPAHandle);
         PhoneAccount mFakeTargetPhoneAccount = makeQuickAccount("cm_acct",
-                PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION);
+                PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION, null);
         when(mMockAccountRegistrar.getPhoneAccountUnchecked(pAHandle)).thenReturn(
                 mFakeTargetPhoneAccount);
         when(mMockCall.getConnectionService()).thenReturn(service);
@@ -269,7 +277,7 @@
         ConnectionServiceWrapper service = makeConnectionServiceWrapper();
         when(mMockCall.getConnectionManagerPhoneAccount()).thenReturn(callManagerPAHandle);
         PhoneAccount mFakeTargetPhoneAccount = makeQuickAccount("cm_acct",
-                PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION);
+                PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION, null);
         when(mMockAccountRegistrar.getPhoneAccountUnchecked(pAHandle)).thenReturn(
                 mFakeTargetPhoneAccount);
         when(mMockCall.getConnectionService()).thenReturn(service);
@@ -315,7 +323,7 @@
         // Do not use this account, even though it is a SIM subscription and can place emergency
         // calls
         ConnectionServiceWrapper service = makeConnectionServiceWrapper();
-        PhoneAccount emergencyPhoneAccount = makeEmergencyPhoneAccount("tel_emer", 0);
+        PhoneAccount emergencyPhoneAccount = makeEmergencyPhoneAccount("tel_emer", 0, null);
         mapToSubSlot(emergencyPhoneAccount, 2 /*subId*/, 1 /*slotId*/);
         phoneAccounts.add(emergencyPhoneAccount);
 
@@ -332,6 +340,36 @@
     }
 
     /**
+     * Ensure that when no phone accounts (visible to the user) are available for the call, we use
+     * an available sim from other another user (on the condition that the user has the
+     * INTERACT_ACROSS_USERS permission).
+     */
+    @SmallTest
+    @Test
+    public void testEmergencyCallAcrossUsers() throws Exception {
+        when(mMockCall.isEmergencyCall()).thenReturn(true);
+        when(mMockCall.isTestEmergencyCall()).thenReturn(false);
+        ConnectionServiceWrapper service = makeConnectionServiceWrapper();
+        // Add an emergency account associated with a different user and expect this to be called.
+        PhoneAccount emergencyPhoneAccount = makeEmergencyPhoneAccount("tel_emer",
+                0, USER_HANDLE_10);
+        mapToSubSlot(emergencyPhoneAccount, 1 /*subId*/, 0 /*slotId*/);
+        phoneAccounts.add(emergencyPhoneAccount);
+        PhoneAccountHandle emergencyPhoneAccountHandle = emergencyPhoneAccount.getAccountHandle();
+
+        mTestCreateConnectionProcessor.process();
+
+        verify(mMockCall).setConnectionManagerPhoneAccount(eq(emergencyPhoneAccountHandle));
+        verify(mMockCall).setTargetPhoneAccount(eq(emergencyPhoneAccountHandle));
+        verify(mMockCall).setConnectionService(eq(service));
+        verify(service).createConnection(eq(mMockCall), any(CreateConnectionResponse.class));
+        // Notify successful connection to call
+        CallIdMapper mockCallIdMapper = mock(CallIdMapper.class);
+        mTestCreateConnectionProcessor.handleCreateConnectionSuccess(mockCallIdMapper, null);
+        verify(mMockCreateConnectionResponse).handleCreateConnectionSuccess(mockCallIdMapper, null);
+    }
+
+    /**
      * Ensure that the non-emergency capable PhoneAccount and the SIM manager is not chosen to place
      * the emergency call if there is an emergency capable PhoneAccount available as well.
      */
@@ -351,7 +389,7 @@
                 "cm_acct", 0);
         phoneAccounts.add(callManagerPA);
         ConnectionServiceWrapper service = makeConnectionServiceWrapper();
-        PhoneAccount emergencyPhoneAccount = makeEmergencyPhoneAccount("tel_emer", 0);
+        PhoneAccount emergencyPhoneAccount = makeEmergencyPhoneAccount("tel_emer", 0, null);
         mapToSubSlot(emergencyPhoneAccount, 2 /*subId*/, 1 /*slotId*/);
         phoneAccounts.add(emergencyPhoneAccount);
         PhoneAccountHandle emergencyPhoneAccountHandle = emergencyPhoneAccount.getAccountHandle();
@@ -387,10 +425,10 @@
                 "cm_acct", 0);
         phoneAccounts.add(callManagerPA);
         ConnectionServiceWrapper service = makeConnectionServiceWrapper();
-        PhoneAccount emergencyPhoneAccount1 = makeEmergencyPhoneAccount("tel_emer1", 0);
+        PhoneAccount emergencyPhoneAccount1 = makeEmergencyPhoneAccount("tel_emer1", 0, null);
         phoneAccounts.add(emergencyPhoneAccount1);
         mapToSubSlot(emergencyPhoneAccount1, 1 /*subId*/, 1 /*slotId*/);
-        PhoneAccount emergencyPhoneAccount2 = makeEmergencyPhoneAccount("tel_emer2", 0);
+        PhoneAccount emergencyPhoneAccount2 = makeEmergencyPhoneAccount("tel_emer2", 0, null);
         phoneAccounts.add(emergencyPhoneAccount2);
         mapToSubSlot(emergencyPhoneAccount2, 2 /*subId*/, 0 /*slotId*/);
         PhoneAccountHandle emergencyPhoneAccountHandle2 = emergencyPhoneAccount2.getAccountHandle();
@@ -418,12 +456,12 @@
         when(mMockCall.isEmergencyCall()).thenReturn(true);
         when(mMockCall.isTestEmergencyCall()).thenReturn(false);
         ConnectionServiceWrapper service = makeConnectionServiceWrapper();
-        PhoneAccount emergencyPhoneAccount1 = makeEmergencyPhoneAccount("tel_emer1", 0);
+        PhoneAccount emergencyPhoneAccount1 = makeEmergencyPhoneAccount("tel_emer1", 0, null);
         mapToSubSlot(emergencyPhoneAccount1, 1 /*subId*/, 0 /*slotId*/);
         setTargetPhoneAccount(mMockCall, emergencyPhoneAccount1.getAccountHandle());
         phoneAccounts.add(emergencyPhoneAccount1);
         PhoneAccount emergencyPhoneAccount2 = makeEmergencyPhoneAccount("tel_emer2",
-                PhoneAccount.CAPABILITY_EMERGENCY_PREFERRED);
+                PhoneAccount.CAPABILITY_EMERGENCY_PREFERRED, null);
         mapToSubSlot(emergencyPhoneAccount2, 2 /*subId*/, 1 /*slotId*/);
         phoneAccounts.add(emergencyPhoneAccount2);
         PhoneAccountHandle emergencyPhoneAccountHandle2 = emergencyPhoneAccount2.getAccountHandle();
@@ -455,10 +493,10 @@
                 "cm_acct", 0);
         phoneAccounts.add(callManagerPA);
         ConnectionServiceWrapper service = makeConnectionServiceWrapper();
-        PhoneAccount emergencyPhoneAccount1 = makeEmergencyPhoneAccount("tel_emer1", 0);
+        PhoneAccount emergencyPhoneAccount1 = makeEmergencyPhoneAccount("tel_emer1", 0, null);
         mapToSubSlot(emergencyPhoneAccount1, 1 /*subId*/, 0 /*slotId*/);
         phoneAccounts.add(emergencyPhoneAccount1);
-        PhoneAccount emergencyPhoneAccount2 = makeEmergencyPhoneAccount("tel_emer2", 0);
+        PhoneAccount emergencyPhoneAccount2 = makeEmergencyPhoneAccount("tel_emer2", 0, null);
         // Make this the user preferred account
         mapToSubSlot(emergencyPhoneAccount2, 2 /*subId*/, 1 /*slotId*/);
         setTargetPhoneAccount(mMockCall, emergencyPhoneAccount2.getAccountHandle());
@@ -492,13 +530,13 @@
                 "cm_acct", 0);
         phoneAccounts.add(callManagerPA);
         ConnectionServiceWrapper service = makeConnectionServiceWrapper();
-        PhoneAccount emergencyPhoneAccount1 = makeEmergencyPhoneAccount("tel_emer1", 0);
+        PhoneAccount emergencyPhoneAccount1 = makeEmergencyPhoneAccount("tel_emer1", 0, null);
         // make this the user preferred account
         setTargetPhoneAccount(mMockCall, emergencyPhoneAccount1.getAccountHandle());
         mapToSubSlot(emergencyPhoneAccount1, 1 /*subId*/,
                 SubscriptionManager.INVALID_SIM_SLOT_INDEX /*slotId*/);
         phoneAccounts.add(emergencyPhoneAccount1);
-        PhoneAccount emergencyPhoneAccount2 = makeEmergencyPhoneAccount("tel_emer2", 0);
+        PhoneAccount emergencyPhoneAccount2 = makeEmergencyPhoneAccount("tel_emer2", 0, null);
         mapToSubSlot(emergencyPhoneAccount2, 2 /*subId*/, 1 /*slotId*/);
         phoneAccounts.add(emergencyPhoneAccount2);
         PhoneAccountHandle emergencyPhoneAccountHandle2 = emergencyPhoneAccount2.getAccountHandle();
@@ -529,11 +567,11 @@
                 "cm_acct", 0);
         phoneAccounts.add(callManagerPA);
         ConnectionServiceWrapper service = makeConnectionServiceWrapper();
-        PhoneAccount emergencyPhoneAccount1 = makeEmergencyPhoneAccount("tel_emer1", 0);
+        PhoneAccount emergencyPhoneAccount1 = makeEmergencyPhoneAccount("tel_emer1", 0, null);
         mapToSubSlot(emergencyPhoneAccount1, 1 /*subId*/,
                 SubscriptionManager.INVALID_SIM_SLOT_INDEX /*slotId*/);
         phoneAccounts.add(emergencyPhoneAccount1);
-        PhoneAccount emergencyPhoneAccount2 = makeEmergencyPhoneAccount("tel_emer2", 0);
+        PhoneAccount emergencyPhoneAccount2 = makeEmergencyPhoneAccount("tel_emer2", 0, null);
         mapToSubSlot(emergencyPhoneAccount2, 2 /*subId*/, 1 /*slotId*/);
         phoneAccounts.add(emergencyPhoneAccount2);
         PhoneAccountHandle emergencyPhoneAccountHandle2 = emergencyPhoneAccount2.getAccountHandle();
@@ -593,7 +631,7 @@
         PhoneAccount emerCallManagerPA = getNewEmergencyConnectionManagerPhoneAccount("cm_acct",
                 PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS);
         ConnectionServiceWrapper service = makeConnectionServiceWrapper();
-        PhoneAccount emergencyPhoneAccount = makeEmergencyPhoneAccount("tel_emer", 0);
+        PhoneAccount emergencyPhoneAccount = makeEmergencyPhoneAccount("tel_emer", 0, null);
         phoneAccounts.add(emergencyPhoneAccount);
         mapToSubSlot(regularAccount, 2 /*subId*/, 1 /*slotId*/);
         mTestCreateConnectionProcessor.process();
@@ -682,7 +720,7 @@
 
     private PhoneAccount makeEmergencyTestPhoneAccount(String id, int capabilities) {
         final PhoneAccount emergencyPhoneAccount = makeQuickAccount(id, capabilities |
-                PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS);
+                PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS, null);
         PhoneAccountHandle emergencyPhoneAccountHandle = emergencyPhoneAccount.getAccountHandle();
         givePhoneAccountBindPermission(emergencyPhoneAccountHandle);
         when(mMockAccountRegistrar.getPhoneAccountUnchecked(emergencyPhoneAccountHandle))
@@ -690,10 +728,11 @@
         return emergencyPhoneAccount;
     }
 
-    private PhoneAccount makeEmergencyPhoneAccount(String id, int capabilities) {
+    private PhoneAccount makeEmergencyPhoneAccount(String id, int capabilities,
+            UserHandle userHandle) {
         final PhoneAccount emergencyPhoneAccount = makeQuickAccount(id, capabilities |
                 PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS |
-                        PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION);
+                        PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION, userHandle);
         PhoneAccountHandle emergencyPhoneAccountHandle = emergencyPhoneAccount.getAccountHandle();
         givePhoneAccountBindPermission(emergencyPhoneAccountHandle);
         when(mMockAccountRegistrar.getPhoneAccountUnchecked(emergencyPhoneAccountHandle))
@@ -702,7 +741,7 @@
     }
 
     private PhoneAccount makePhoneAccount(String id, int capabilities) {
-        final PhoneAccount phoneAccount = makeQuickAccount(id, capabilities);
+        final PhoneAccount phoneAccount = makeQuickAccount(id, capabilities, null);
         PhoneAccountHandle phoneAccountHandle = phoneAccount.getAccountHandle();
         givePhoneAccountBindPermission(phoneAccountHandle);
         when(mMockAccountRegistrar.getPhoneAccountUnchecked(
@@ -720,7 +759,7 @@
     }
 
     private PhoneAccountHandle getNewConnectionMangerHandleForCall(Call call, String id) {
-        PhoneAccountHandle callManagerPAHandle = makeQuickAccountHandle(id);
+        PhoneAccountHandle callManagerPAHandle = makeQuickAccountHandle(id, null);
         when(mMockAccountRegistrar.getSimCallManagerFromCall(eq(call))).thenReturn(
                 callManagerPAHandle);
         givePhoneAccountBindPermission(callManagerPAHandle);
@@ -728,7 +767,7 @@
     }
 
     private PhoneAccountHandle getNewTargetPhoneAccountHandle(String id) {
-        PhoneAccountHandle pAHandle = makeQuickAccountHandle(id);
+        PhoneAccountHandle pAHandle = makeQuickAccountHandle(id, null);
         givePhoneAccountBindPermission(pAHandle);
         return pAHandle;
     }
@@ -739,7 +778,7 @@
 
     private PhoneAccount createNewConnectionManagerPhoneAccountForCall(Call call, String id,
             int capability) {
-        PhoneAccount callManagerPA = makeQuickAccount(id, capability);
+        PhoneAccount callManagerPA = makeQuickAccount(id, capability, null);
         when(mMockAccountRegistrar.getSimCallManagerFromCall(eq(call))).thenReturn(
                 callManagerPA.getAccountHandle());
         givePhoneAccountBindPermission(callManagerPA.getAccountHandle());
@@ -749,7 +788,7 @@
     }
 
     private PhoneAccount getNewEmergencyConnectionManagerPhoneAccount(String id, int capability) {
-        PhoneAccount callManagerPA = makeQuickAccount(id, capability);
+        PhoneAccount callManagerPA = makeQuickAccount(id, capability, null);
         when(mMockAccountRegistrar.getSimCallManagerOfCurrentUser()).thenReturn(
                 callManagerPA.getAccountHandle());
         givePhoneAccountBindPermission(callManagerPA.getAccountHandle());
@@ -766,21 +805,24 @@
         ConnectionServiceWrapper wrapper = mock(ConnectionServiceWrapper.class);
         when(mMockConnectionServiceRepository.getService(
                 eq(makeQuickConnectionServiceComponentName()),
-                eq(Binder.getCallingUserHandle()))).thenReturn(wrapper);
+                any(UserHandle.class))).thenReturn(wrapper);
         return wrapper;
     }
 
-    private static PhoneAccountHandle makeQuickAccountHandle(String id) {
-        return new PhoneAccountHandle(makeQuickConnectionServiceComponentName(), id,
-                Binder.getCallingUserHandle());
+    private static PhoneAccountHandle makeQuickAccountHandle(String id, UserHandle userHandle) {
+        if (userHandle == null) {
+            userHandle = Binder.getCallingUserHandle();
+        }
+        return new PhoneAccountHandle(makeQuickConnectionServiceComponentName(), id, userHandle);
     }
 
-    private PhoneAccount.Builder makeQuickAccountBuilder(String id, int idx) {
-        return new PhoneAccount.Builder(makeQuickAccountHandle(id), "label" + idx);
+    private PhoneAccount.Builder makeQuickAccountBuilder(String id, int idx,
+            UserHandle userHandle) {
+        return new PhoneAccount.Builder(makeQuickAccountHandle(id, userHandle), "label" + idx);
     }
 
-    private PhoneAccount makeQuickAccount(String id, int idx) {
-        return makeQuickAccountBuilder(id, idx)
+    private PhoneAccount makeQuickAccount(String id, int idx, UserHandle userHandle) {
+        return makeQuickAccountBuilder(id, idx, userHandle)
                 .setAddress(Uri.parse("http://foo.com/" + idx))
                 .setSubscriptionAddress(Uri.parse("tel:555-000" + idx))
                 .setCapabilities(idx)
diff --git a/tests/src/com/android/server/telecom/tests/DndCallFilteringTests.java b/tests/src/com/android/server/telecom/tests/DndCallFilteringTests.java
new file mode 100644
index 0000000..4885d61
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/DndCallFilteringTests.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2022 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.server.telecom.tests;
+
+import android.net.Uri;
+
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.TimeUnit;
+
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.Ringer;
+import com.android.server.telecom.callfiltering.CallFilteringResult;
+import com.android.server.telecom.callfiltering.DndCallFilter;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.when;
+
+import junit.framework.Assert;
+
+@RunWith(JUnit4.class)
+public class DndCallFilteringTests extends TelecomTestCase {
+
+    // mocks
+    @Mock private Call mCall;
+    @Mock private Ringer mRinger;
+    // constants
+    private final long FILTER_TIMEOUT = 2000;
+
+    private final CallFilteringResult BASE_RESULT = new CallFilteringResult.Builder()
+            .setShouldAllowCall(true)
+            .setShouldAddToCallLog(true)
+            .setShouldShowNotification(true)
+            .build();
+
+
+    private final CallFilteringResult CALL_SUPPRESSED_RESULT = new CallFilteringResult.Builder()
+            .setShouldAllowCall(true)
+            .setShouldAddToCallLog(true)
+            .setShouldShowNotification(true)
+            .setDndSuppressed(true)
+            .build();
+
+    private final CallFilteringResult CALL_NOT_SUPPRESSED_RESULT = new CallFilteringResult.Builder()
+            .setShouldAllowCall(true)
+            .setShouldAddToCallLog(true)
+            .setShouldShowNotification(true)
+            .setDndSuppressed(false)
+            .build();
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        // Dynamic variables
+        Uri testHandle = Uri.parse("tel:1235551234");
+        when(mCall.getHandle()).thenReturn(testHandle);
+        when(mCall.wasDndCheckComputedForCall()).thenReturn(false);
+    }
+
+    @Override
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    /**
+     * Test DndCallFilter suppresses a call and builds a CALL_SUPPRESSED_RESULT when given
+     * a false shouldRingForContact answer.
+     *
+     * @throws Exception; should not throw
+     */
+    @Test
+    public void testShouldSuppressCall() throws Exception {
+        // GIVEN
+        DndCallFilter filter = new DndCallFilter(mCall, mRinger);
+
+        // WHEN
+        assertNotNull(filter);
+        when(mRinger.shouldRingForContact(mCall)).thenReturn(false);
+
+        // THEN
+        CompletionStage<CallFilteringResult> resultFuture = filter.startFilterLookup(BASE_RESULT);
+
+        Assert.assertEquals(CALL_SUPPRESSED_RESULT, resultFuture.toCompletableFuture()
+                .get(FILTER_TIMEOUT, TimeUnit.MILLISECONDS));
+    }
+
+    /**
+     * Test DndCallFilter allows a call to ring and builds a CALL_NOT_SUPPRESSED_RESULT when
+     * given a true shouldRingForContact answer.
+     *
+     * @throws Exception; should not throw
+     */
+    @Test
+    public void testCallShouldRingAndNotBeSuppressed() throws Exception {
+        // GIVEN
+        DndCallFilter filter = new DndCallFilter(mCall, mRinger);
+
+        // WHEN
+        assertNotNull(filter);
+        when(mRinger.shouldRingForContact(mCall)).thenReturn(true);
+
+        // THEN
+        CompletionStage<CallFilteringResult> resultFuture = filter.startFilterLookup(BASE_RESULT);
+
+        // ASSERT
+        Assert.assertEquals(CALL_NOT_SUPPRESSED_RESULT, resultFuture.toCompletableFuture()
+                .get(FILTER_TIMEOUT, TimeUnit.MILLISECONDS));
+    }
+}
\ No newline at end of file
diff --git a/tests/src/com/android/server/telecom/tests/EmergencyCallDiagnosticLoggerTest.java b/tests/src/com/android/server/telecom/tests/EmergencyCallDiagnosticLoggerTest.java
new file mode 100644
index 0000000..3cb8196
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/EmergencyCallDiagnosticLoggerTest.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2023 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.server.telecom.tests;
+
+
+import static android.telephony.TelephonyManager.EmergencyCallDiagnosticParams;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.os.BugreportManager;
+import android.os.DropBoxManager;
+import android.telecom.DisconnectCause;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telephony.TelephonyManager;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.CallerInfoLookupHelper;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.ClockProxy;
+import com.android.server.telecom.EmergencyCallDiagnosticLogger;
+import com.android.server.telecom.PhoneAccountRegistrar;
+import com.android.server.telecom.PhoneNumberUtilsAdapter;
+import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.Timeouts;
+import com.android.server.telecom.ui.ToastFactory;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+@RunWith(JUnit4.class)
+public class EmergencyCallDiagnosticLoggerTest extends TelecomTestCase {
+
+    private static final ComponentName COMPONENT_NAME_1 = ComponentName
+            .unflattenFromString("com.foo/.Blah");
+    private static final PhoneAccountHandle SIM_1_HANDLE = new PhoneAccountHandle(
+            COMPONENT_NAME_1, "Sim1");
+    private static final PhoneAccount SIM_1_ACCOUNT = new PhoneAccount.
+            Builder(SIM_1_HANDLE, "Sim1")
+            .setCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION
+                    | PhoneAccount.CAPABILITY_CALL_PROVIDER)
+            .setIsEnabled(true)
+            .build();
+    private static final String DROP_BOX_TAG = "ecall_diagnostic_data";
+
+    private static final long EMERGENCY_CALL_ACTIVE_TIME_THRESHOLD_MILLIS = 100L;
+
+    private static final long EMERGENCY_CALL_TIME_BEFORE_USER_DISCONNECT_THRESHOLD_MILLIS = 120L;
+
+    private static final int DAYS_BACK_TO_SEARCH_EMERGENCY_DIAGNOSTIC_ENTRIES = 1;
+    private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() {
+    };
+    EmergencyCallDiagnosticLogger mEmergencyCallDiagnosticLogger;
+    @Mock
+    private Timeouts.Adapter mTimeouts;
+    @Mock
+    private CallsManager mMockCallsManager;
+    @Mock
+    private CallerInfoLookupHelper mMockCallerInfoLookupHelper;
+    @Mock
+    private PhoneAccountRegistrar mMockPhoneAccountRegistrar;
+    @Mock
+    private ClockProxy mMockClockProxy;
+    @Mock
+    private ToastFactory mMockToastProxy;
+    @Mock
+    private PhoneNumberUtilsAdapter mMockPhoneNumberUtilsAdapter;
+
+    @Mock
+    private TelephonyManager mTm;
+    @Mock
+    private BugreportManager mBrm;
+    @Mock
+    private DropBoxManager mDbm;
+
+    @Mock
+    private ClockProxy mClockProxy;
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        doReturn(mMockCallerInfoLookupHelper).when(mMockCallsManager).getCallerInfoLookupHelper();
+        doReturn(mMockPhoneAccountRegistrar).when(mMockCallsManager).getPhoneAccountRegistrar();
+        doReturn(SIM_1_ACCOUNT).when(mMockPhoneAccountRegistrar).getPhoneAccountUnchecked(
+                eq(SIM_1_HANDLE));
+        when(mTimeouts.getEmergencyCallActiveTimeThresholdMillis()).
+                thenReturn(EMERGENCY_CALL_ACTIVE_TIME_THRESHOLD_MILLIS);
+        when(mTimeouts.getEmergencyCallTimeBeforeUserDisconnectThresholdMillis()).
+                thenReturn(EMERGENCY_CALL_TIME_BEFORE_USER_DISCONNECT_THRESHOLD_MILLIS);
+        when(mTimeouts.getDaysBackToSearchEmergencyDiagnosticEntries()).
+                thenReturn(DAYS_BACK_TO_SEARCH_EMERGENCY_DIAGNOSTIC_ENTRIES);
+        when(mClockProxy.currentTimeMillis()).thenReturn(System.currentTimeMillis());
+
+        mEmergencyCallDiagnosticLogger = new EmergencyCallDiagnosticLogger(mTm, mBrm,
+                mTimeouts, mDbm, Runnable::run, mClockProxy);
+    }
+
+    @Override
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+        //reset(mTm);
+    }
+
+    /**
+     * Helper function that creates the call being tested.
+     * Also invokes onStartCreateConnection
+     */
+    private Call createCall(boolean isEmergencyCall, int direction) {
+        Call call = getCall();
+        call.setCallDirection(direction);
+        call.setIsEmergencyCall(isEmergencyCall);
+        mEmergencyCallDiagnosticLogger.onStartCreateConnection(call);
+        return call;
+    }
+
+    /**
+     * @return an instance of {@link Call} for testing purposes.
+     */
+    private Call getCall() {
+        return new Call(
+                "1", /* callId */
+                mContext,
+                mMockCallsManager,
+                mLock,
+                null /* ConnectionServiceRepository */,
+                mMockPhoneNumberUtilsAdapter,
+                Uri.parse("tel:6505551212"),
+                null /* GatewayInfo */,
+                null /* connectionManagerPhoneAccountHandle */,
+                SIM_1_HANDLE,
+                Call.CALL_DIRECTION_OUTGOING,
+                false /* shouldAttachToExistingConnection*/,
+                false /* isConference */,
+                mMockClockProxy,
+                mMockToastProxy);
+    }
+
+    /**
+     * Test that only outgoing emergency calls are tracked
+     */
+    @Test
+    public void testNonEmergencyCallNotTracked() {
+        //should not be tracked
+        createCall(false, Call.CALL_DIRECTION_OUTGOING);
+        assertEquals(0, mEmergencyCallDiagnosticLogger.getEmergencyCallsMap().size());
+
+        //should not be tracked (not in scope)
+        createCall(false, Call.CALL_DIRECTION_INCOMING);
+        assertEquals(0, mEmergencyCallDiagnosticLogger.getEmergencyCallsMap().size());
+    }
+
+    /**
+     * Test that incoming emergency calls are not tracked (not in scope right now)
+     */
+    @Test
+    public void testIncomingEmergencyCallsNotTracked() {
+        //should not be tracked
+        createCall(true, Call.CALL_DIRECTION_INCOMING);
+        assertEquals(0, mEmergencyCallDiagnosticLogger.getEmergencyCallsMap().size());
+    }
+
+
+    /**
+     * Test getDataCollectionTypes(reason)
+     */
+    @Test
+    public void testCollectionTypeForReasonDoesNotReturnUnreasonableValues() {
+        int reason = EmergencyCallDiagnosticLogger.REPORT_REASON_RANGE_START + 1;
+        while (reason < EmergencyCallDiagnosticLogger.REPORT_REASON_RANGE_END) {
+            List<Integer> ctypes = EmergencyCallDiagnosticLogger.getDataCollectionTypes(reason);
+            assertNotNull(ctypes);
+            Set<Integer> ctypesSet = new HashSet<>(ctypes);
+
+            //assert that list is not empty
+            assertNotEquals(0, ctypes.size());
+
+            //assert no repeated values
+            assertEquals(ctypes.size(), ctypesSet.size());
+
+            //if bugreport type is present, that should be the only collection type
+            if (ctypesSet.contains(EmergencyCallDiagnosticLogger.COLLECTION_TYPE_BUGREPORT)) {
+                assertEquals(1, ctypes.size());
+            }
+            reason++;
+        }
+    }
+
+
+    /**
+     * Test emergency call reported stuck
+     */
+    @Test
+    public void testStuckEmergencyCall() {
+        Call call = createCall(true, Call.CALL_DIRECTION_OUTGOING);
+        mEmergencyCallDiagnosticLogger.onCallAdded(call);
+        mEmergencyCallDiagnosticLogger.reportStuckCall(call);
+
+        //for stuck calls, we should always be persisting some data
+        ArgumentCaptor<EmergencyCallDiagnosticParams> captor =
+                ArgumentCaptor.forClass(EmergencyCallDiagnosticParams.class);
+        verify(mTm, times(1)).persistEmergencyCallDiagnosticData(eq(DROP_BOX_TAG),
+                captor.capture());
+        EmergencyCallDiagnosticParams dp = captor.getValue();
+
+        assertNotNull(dp);
+        assertTrue(
+                dp.isLogcatCollectionEnabled() || dp.isTelecomDumpSysCollectionEnabled()
+                        || dp.isTelephonyDumpSysCollectionEnabled());
+
+        //tracking should end
+        assertEquals(0, mEmergencyCallDiagnosticLogger.getEmergencyCallsMap().size());
+    }
+
+    @Test
+    public void testEmergencyCallNeverWentActiveWithNonLocalDisconnectCause() {
+        Call call = createCall(true, Call.CALL_DIRECTION_OUTGOING);
+        mEmergencyCallDiagnosticLogger.onCallAdded(call);
+
+        //call is tracked
+        assertEquals(1, mEmergencyCallDiagnosticLogger.getEmergencyCallsMap().size());
+
+        call.setDisconnectCause(new DisconnectCause(DisconnectCause.REJECTED));
+        mEmergencyCallDiagnosticLogger.onCallRemoved(call);
+
+        //for non-local disconnect of non-active call,  we should always be persisting some data
+        ArgumentCaptor<TelephonyManager.EmergencyCallDiagnosticParams> captor =
+                ArgumentCaptor.forClass(
+                        TelephonyManager.EmergencyCallDiagnosticParams.class);
+        verify(mTm, times(1)).persistEmergencyCallDiagnosticData(eq(DROP_BOX_TAG),
+                captor.capture());
+        TelephonyManager.EmergencyCallDiagnosticParams dp = captor.getValue();
+
+        assertNotNull(dp);
+        assertTrue(
+                dp.isLogcatCollectionEnabled() || dp.isTelecomDumpSysCollectionEnabled()
+                        || dp.isTelephonyDumpSysCollectionEnabled());
+
+        //tracking should end
+        assertEquals(0, mEmergencyCallDiagnosticLogger.getEmergencyCallsMap().size());
+    }
+
+    @Test
+    public void testEmergencyCallWentActiveForLongDuration_shouldNotCollectDiagnostics()
+            throws Exception {
+        Call call = createCall(true, Call.CALL_DIRECTION_OUTGOING);
+        mEmergencyCallDiagnosticLogger.onCallAdded(call);
+
+        //call went active
+        mEmergencyCallDiagnosticLogger.onCallStateChanged(call, CallState.DIALING,
+                CallState.ACTIVE);
+
+        //return large value for time when call is disconnected
+        when(mClockProxy.currentTimeMillis()).thenReturn(System.currentTimeMillis() + 10000L);
+
+        call.setDisconnectCause(new DisconnectCause(DisconnectCause.ERROR));
+        mEmergencyCallDiagnosticLogger.onCallRemoved(call);
+
+        //no diagnostic data should be persisted
+        verify(mTm, never()).persistEmergencyCallDiagnosticData(eq(DROP_BOX_TAG),
+                any());
+
+        //tracking should end
+        assertEquals(0, mEmergencyCallDiagnosticLogger.getEmergencyCallsMap().size());
+    }
+
+}
diff --git a/tests/src/com/android/server/telecom/tests/EmergencyCallHelperTest.java b/tests/src/com/android/server/telecom/tests/EmergencyCallHelperTest.java
new file mode 100644
index 0000000..380e327
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/EmergencyCallHelperTest.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2022 Tc
+ *
+ * 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.server.telecom.tests;
+
+import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION;
+import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.DefaultDialerCache;
+import com.android.server.telecom.EmergencyCallHelper;
+import com.android.server.telecom.Timeouts;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(JUnit4.class)
+public class EmergencyCallHelperTest extends TelecomTestCase {
+  private static final String SYSTEM_DIALER_PACKAGE = "abc.xyz";
+  private EmergencyCallHelper mEmergencyCallHelper;
+  @Mock
+  private PackageManager mPackageManager;
+  @Mock
+  private DefaultDialerCache mDefaultDialerCache;
+  @Mock
+  private Timeouts.Adapter mTimeoutsAdapter;
+  @Mock
+  private UserHandle mUserHandle;
+  @Mock
+  private Call mCall;
+
+  @Override
+  @Before
+  public void setUp() throws Exception {
+    super.setUp();
+    MockitoAnnotations.initMocks(this);
+    mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
+    when(mContext.getPackageManager()).thenReturn(mPackageManager);
+    mEmergencyCallHelper = new EmergencyCallHelper(mContext, mDefaultDialerCache,
+        mTimeoutsAdapter);
+    when(mDefaultDialerCache.getSystemDialerApplication()).thenReturn(SYSTEM_DIALER_PACKAGE);
+
+    //start with no perms
+    when(mPackageManager.checkPermission(eq(ACCESS_BACKGROUND_LOCATION),
+        eq(SYSTEM_DIALER_PACKAGE))).thenReturn(
+        PackageManager.PERMISSION_DENIED);
+
+    when(mPackageManager.checkPermission(eq(ACCESS_FINE_LOCATION),
+        eq(SYSTEM_DIALER_PACKAGE))).thenReturn(
+        PackageManager.PERMISSION_DENIED);
+
+    when(mCall.isEmergencyCall()).thenReturn(true);
+    when(mContext.getResources().getBoolean(R.bool.grant_location_permission_enabled)).thenReturn(
+        true);
+  }
+
+  @Override
+  @After
+  public void tearDown() throws Exception {
+    super.tearDown();
+  }
+
+  private void verifyRevokeInvokedFor(String perm) {
+    verify(mPackageManager, times(1)).revokeRuntimePermission(eq(SYSTEM_DIALER_PACKAGE),
+        eq(perm), eq(mUserHandle));
+  }
+
+  private void verifyRevokeNotInvokedFor(String perm) {
+    verify(mPackageManager, never()).revokeRuntimePermission(eq(SYSTEM_DIALER_PACKAGE),
+        eq(perm), eq(mUserHandle));
+  }
+
+  private void verifyGrantInvokedFor(String perm) {
+    verify(mPackageManager, times(1)).grantRuntimePermission(
+        nullable(String.class),
+        eq(perm), eq(mUserHandle));
+  }
+
+  private void verifyGrantNotInvokedFor(String perm) {
+    verify(mPackageManager, never()).grantRuntimePermission(
+        nullable(String.class),
+        eq(perm), eq(mUserHandle));
+  }
+
+  @SmallTest
+  @Test
+  public void testEmergencyCallHelperRevokesOnlyFinePermAfterBackgroundPermGrantException() {
+
+    //granting of background location perm fails
+    doThrow(new SecurityException()).when(mPackageManager).grantRuntimePermission(
+        nullable(String.class),
+        eq(ACCESS_BACKGROUND_LOCATION), eq(mUserHandle));
+
+    mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(mCall, mUserHandle);
+    mEmergencyCallHelper.maybeRevokeTemporaryLocationPermission();
+
+    verifyGrantInvokedFor(ACCESS_BACKGROUND_LOCATION);
+    verifyGrantInvokedFor(ACCESS_FINE_LOCATION);
+    //only fine perm should be revoked
+    verifyRevokeNotInvokedFor(ACCESS_BACKGROUND_LOCATION);
+    verifyRevokeInvokedFor(ACCESS_FINE_LOCATION);
+  }
+
+  @SmallTest
+  @Test
+  public void testEmergencyCallHelperRevokesOnlyBackgroundPermAfterFinePermGrantException() {
+
+    //granting of fine location perm fails
+    doThrow(new SecurityException()).when(mPackageManager).grantRuntimePermission(
+        nullable(String.class),
+        eq(ACCESS_FINE_LOCATION), eq(mUserHandle));
+
+    mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(mCall, mUserHandle);
+    mEmergencyCallHelper.maybeRevokeTemporaryLocationPermission();
+
+    //only background perm should be revoked
+    verifyGrantInvokedFor(ACCESS_BACKGROUND_LOCATION);
+    verifyGrantInvokedFor(ACCESS_FINE_LOCATION);
+    //only fine perm should be revoked
+    verifyRevokeNotInvokedFor(ACCESS_FINE_LOCATION);
+    verifyRevokeInvokedFor(ACCESS_BACKGROUND_LOCATION);
+  }
+
+  @SmallTest
+  @Test
+  public void testNoPermGrantWhenPackageHasAllPerms() {
+
+    when(mPackageManager.checkPermission(eq(ACCESS_BACKGROUND_LOCATION),
+        eq(SYSTEM_DIALER_PACKAGE))).thenReturn(
+        PackageManager.PERMISSION_GRANTED);
+
+    when(mPackageManager.checkPermission(eq(ACCESS_FINE_LOCATION),
+        eq(SYSTEM_DIALER_PACKAGE))).thenReturn(
+        PackageManager.PERMISSION_GRANTED);
+
+    mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(mCall, mUserHandle);
+    mEmergencyCallHelper.maybeRevokeTemporaryLocationPermission();
+
+    //permissions should neither be granted or revoked
+    verifyGrantNotInvokedFor(ACCESS_BACKGROUND_LOCATION);
+    verifyGrantNotInvokedFor(ACCESS_FINE_LOCATION);
+    verifyRevokeNotInvokedFor(ACCESS_BACKGROUND_LOCATION);
+    verifyRevokeNotInvokedFor(ACCESS_FINE_LOCATION);
+  }
+
+  @SmallTest
+  @Test
+  public void testNoPermGrantForNonEmergencyCall() {
+
+    when(mCall.isEmergencyCall()).thenReturn(false);
+
+    mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(mCall, mUserHandle);
+    mEmergencyCallHelper.maybeRevokeTemporaryLocationPermission();
+
+    //permissions should neither be granted or revoked
+    verifyGrantNotInvokedFor(ACCESS_BACKGROUND_LOCATION);
+    verifyGrantNotInvokedFor(ACCESS_FINE_LOCATION);
+    verifyRevokeNotInvokedFor(ACCESS_BACKGROUND_LOCATION);
+    verifyRevokeNotInvokedFor(ACCESS_FINE_LOCATION);
+  }
+
+  @SmallTest
+  @Test
+  public void testNoPermGrantWhenGrantLocationPermissionIsFalse() {
+
+    when(mContext.getResources().getBoolean(R.bool.grant_location_permission_enabled)).thenReturn(
+        false);
+
+    mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(mCall, mUserHandle);
+    mEmergencyCallHelper.maybeRevokeTemporaryLocationPermission();
+
+    //permissions should neither be granted or revoked
+    verifyGrantNotInvokedFor(ACCESS_BACKGROUND_LOCATION);
+    verifyGrantNotInvokedFor(ACCESS_FINE_LOCATION);
+    verifyRevokeNotInvokedFor(ACCESS_BACKGROUND_LOCATION);
+    verifyRevokeNotInvokedFor(ACCESS_FINE_LOCATION);
+  }
+
+  @SmallTest
+  @Test
+  public void testOnlyFineLocationPermIsGrantedAndRevoked() {
+
+    when(mPackageManager.checkPermission(eq(ACCESS_BACKGROUND_LOCATION),
+        eq(SYSTEM_DIALER_PACKAGE))).thenReturn(
+        PackageManager.PERMISSION_GRANTED);
+
+    mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(mCall, mUserHandle);
+    mEmergencyCallHelper.maybeRevokeTemporaryLocationPermission();
+
+    //permissions should neither be granted or revoked
+    verifyGrantNotInvokedFor(ACCESS_BACKGROUND_LOCATION);
+    verifyGrantInvokedFor(ACCESS_FINE_LOCATION);
+    verifyRevokeNotInvokedFor(ACCESS_BACKGROUND_LOCATION);
+    verifyRevokeInvokedFor(ACCESS_FINE_LOCATION);
+  }
+
+  @SmallTest
+  @Test
+  public void testOnlyBackgroundLocationPermIsGrantedAndRevoked() {
+
+    when(mPackageManager.checkPermission(eq(ACCESS_FINE_LOCATION),
+        eq(SYSTEM_DIALER_PACKAGE))).thenReturn(
+        PackageManager.PERMISSION_GRANTED);
+
+    mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(mCall, mUserHandle);
+    mEmergencyCallHelper.maybeRevokeTemporaryLocationPermission();
+
+    //permissions should neither be granted or revoked
+    verifyGrantNotInvokedFor(ACCESS_FINE_LOCATION);
+    verifyGrantInvokedFor(ACCESS_BACKGROUND_LOCATION);
+    verifyRevokeNotInvokedFor(ACCESS_FINE_LOCATION);
+    verifyRevokeInvokedFor(ACCESS_BACKGROUND_LOCATION);
+  }
+}
diff --git a/tests/src/com/android/server/telecom/tests/HeadsetMediaButtonTest.java b/tests/src/com/android/server/telecom/tests/HeadsetMediaButtonTest.java
index 6d15e60..ce23724 100644
--- a/tests/src/com/android/server/telecom/tests/HeadsetMediaButtonTest.java
+++ b/tests/src/com/android/server/telecom/tests/HeadsetMediaButtonTest.java
@@ -16,20 +16,34 @@
 
 package com.android.server.telecom.tests;
 
+import android.content.Intent;
+import android.media.session.MediaSession;
+import android.telecom.CallEndpoint;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.KeyEvent;
+
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.HeadsetMediaButton;
 import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.HeadsetMediaButton.MediaSessionWrapper;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -38,6 +52,7 @@
     private static final int TEST_TIMEOUT_MILLIS = 1000;
 
     private HeadsetMediaButton mHeadsetMediaButton;
+    private MediaSession.Callback mSessionCallback;
 
     @Mock private CallsManager mMockCallsManager;
     @Mock private HeadsetMediaButton.MediaSessionAdapter mMediaSessionAdapter;
@@ -47,8 +62,15 @@
     @Before
     public void setUp() throws Exception {
         super.setUp();
+
+        ArgumentCaptor<MediaSession.Callback> sessionCallbackArgument =
+                ArgumentCaptor.forClass(MediaSession.Callback.class);
+
         mHeadsetMediaButton = new HeadsetMediaButton(mContext, mMockCallsManager, mLock,
                 mMediaSessionAdapter);
+
+        verify(mMediaSessionAdapter).setCallback(sessionCallbackArgument.capture());
+        mSessionCallback = sessionCallbackArgument.getValue();
     }
 
     @Override
@@ -59,8 +81,9 @@
     }
 
     /**
-     * Nominal case; just add a call and remove it.
+     * Nominal case; just add a call and remove it; this happens when the audio state is earpiece.
      */
+    @SmallTest
     @Test
     public void testAddCall() {
         Call regularCall = getRegularCall();
@@ -68,19 +91,101 @@
         when(mMockCallsManager.hasAnyCalls()).thenReturn(true);
         mHeadsetMediaButton.onCallAdded(regularCall);
         waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+        verify(mMediaSessionAdapter, never()).setActive(eq(true));
+
+        // Report that the endpoint is earpiece and other routes that don't matter
+        mHeadsetMediaButton.onCallEndpointChanged(
+                new CallEndpoint("Earpiece", CallEndpoint.TYPE_EARPIECE));
+        mHeadsetMediaButton.onCallEndpointChanged(
+                new CallEndpoint("Speaker", CallEndpoint.TYPE_SPEAKER));
+        mHeadsetMediaButton.onCallEndpointChanged(
+                new CallEndpoint("BT", CallEndpoint.TYPE_BLUETOOTH));
+        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+        verify(mMediaSessionAdapter, never()).setActive(eq(true));
+
+        // ... and thus we see how the original code isn't amenable to tests.
+        when(mMediaSessionAdapter.isActive()).thenReturn(false);
+
+        // Still should not have done anything; we never hit wired headset
+        mHeadsetMediaButton.onCallRemoved(regularCall);
+        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+        verify(mMediaSessionAdapter, never()).setActive(eq(false));
+    }
+
+    /**
+     * Call is added and then routed to headset after call start
+     */
+    @SmallTest
+    @Test
+    public void testAddCallThenRouteToHeadset() {
+        Call regularCall = getRegularCall();
+
+        mHeadsetMediaButton.onCallAdded(regularCall);
+        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+        verify(mMediaSessionAdapter, never()).setActive(eq(true));
+
+        mHeadsetMediaButton.onCallEndpointChanged(
+                new CallEndpoint("Wired Headset", CallEndpoint.TYPE_WIRED_HEADSET));
+        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
         verify(mMediaSessionAdapter).setActive(eq(true));
+
         // ... and thus we see how the original code isn't amenable to tests.
         when(mMediaSessionAdapter.isActive()).thenReturn(true);
 
-        when(mMockCallsManager.hasAnyCalls()).thenReturn(false);
+        // Remove the one call; we should release the session.
         mHeadsetMediaButton.onCallRemoved(regularCall);
         waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
         verify(mMediaSessionAdapter).setActive(eq(false));
+        when(mMediaSessionAdapter.isActive()).thenReturn(false);
+
+        // Add a new call; make sure we go active once more.
+        mHeadsetMediaButton.onCallAdded(regularCall);
+        mHeadsetMediaButton.onCallEndpointChanged(
+                new CallEndpoint("Wired Headset", CallEndpoint.TYPE_WIRED_HEADSET));
+        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+        verify(mMediaSessionAdapter, times(2)).setActive(eq(true));
+    }
+
+    /**
+     * Call is added and then routed to headset after call start
+     */
+    @SmallTest
+    @Test
+    public void testAddCallThenRouteToHeadsetAndBack() {
+        Call regularCall = getRegularCall();
+
+        when(mMockCallsManager.hasAnyCalls()).thenReturn(true);
+        mHeadsetMediaButton.onCallAdded(regularCall);
+        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+        verify(mMediaSessionAdapter, never()).setActive(eq(true));
+
+        mHeadsetMediaButton.onCallEndpointChanged(
+                new CallEndpoint("Wired Headset", CallEndpoint.TYPE_WIRED_HEADSET));
+        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+        verify(mMediaSessionAdapter).setActive(eq(true));
+
+        // ... and thus we see how the original code isn't amenable to tests.
+        when(mMediaSessionAdapter.isActive()).thenReturn(true);
+
+        mHeadsetMediaButton.onCallEndpointChanged(
+                new CallEndpoint("Earpiece", CallEndpoint.TYPE_EARPIECE));
+        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+        verify(mMediaSessionAdapter).setActive(eq(false));
+        when(mMediaSessionAdapter.isActive()).thenReturn(false);
+
+        // Remove the one call; we should not release again.
+        mHeadsetMediaButton.onCallRemoved(regularCall);
+        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+        // Remember, mockito counts total invocations; we should have went active once and then
+        // inactive again when we hit earpiece.
+        verify(mMediaSessionAdapter, times(1)).setActive(eq(true));
+        verify(mMediaSessionAdapter, times(1)).setActive(eq(false));
     }
 
     /**
      * Test a case where a regular call becomes an external call, and back again.
      */
+    @SmallTest
     @Test
     public void testRegularCallThatBecomesExternal() {
         Call regularCall = getRegularCall();
@@ -88,6 +193,8 @@
         // Start with a regular old call.
         when(mMockCallsManager.hasAnyCalls()).thenReturn(true);
         mHeadsetMediaButton.onCallAdded(regularCall);
+        mHeadsetMediaButton.onCallEndpointChanged(
+                new CallEndpoint("Wired Headset", CallEndpoint.TYPE_WIRED_HEADSET));
         waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
         verify(mMediaSessionAdapter).setActive(eq(true));
         when(mMediaSessionAdapter.isActive()).thenReturn(true);
@@ -99,6 +206,7 @@
         // Expect to set session inactive.
         waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
         verify(mMediaSessionAdapter).setActive(eq(false));
+        when(mMediaSessionAdapter.isActive()).thenReturn(false);
 
         // For good measure lets make it non-external again.
         when(regularCall.isExternalCall()).thenReturn(false);
@@ -106,7 +214,88 @@
         mHeadsetMediaButton.onExternalCallChanged(regularCall, false);
         // Expect to set session active.
         waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
-        verify(mMediaSessionAdapter).setActive(eq(true));
+        verify(mMediaSessionAdapter, times(2)).setActive(eq(true));
+    }
+
+    @MediumTest
+    @Test
+    public void testExternalCallNotChangesState() {
+        Call externalCall = getRegularCall();
+        when(externalCall.isExternalCall()).thenReturn(true);
+
+        mHeadsetMediaButton.onCallAdded(externalCall);
+        mHeadsetMediaButton.onCallEndpointChanged(
+                new CallEndpoint("Wired Headset", CallEndpoint.TYPE_WIRED_HEADSET));
+        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+        verify(mMediaSessionAdapter, never()).setActive(eq(true));
+
+        mHeadsetMediaButton.onCallRemoved(externalCall);
+        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+        verify(mMediaSessionAdapter, never()).setActive(eq(false));
+    }
+
+    @SmallTest
+    @Test
+    public void testCallbackReceivesKeyEventUnaware() {
+        mSessionCallback.onMediaButtonEvent(getKeyEventIntent(
+                KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_0, false));
+        verify(mMockCallsManager, never()).onMediaButton(anyInt());
+    }
+
+    @SmallTest
+    @Test
+    public void testCallbackReceivesKeyEventShortClick() {
+        mSessionCallback.onMediaButtonEvent(getKeyEventIntent(
+                KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_HEADSETHOOK, false));
+        verify(mMockCallsManager, never()).onMediaButton(anyInt());
+
+        mSessionCallback.onMediaButtonEvent(getKeyEventIntent(
+                KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HEADSETHOOK, false));
+        verify(mMockCallsManager, times(1)).onMediaButton(HeadsetMediaButton.SHORT_PRESS);
+    }
+
+    @SmallTest
+    @Test
+    public void testCallbackReceivesKeyEventLongClick() {
+        mSessionCallback.onMediaButtonEvent(getKeyEventIntent(
+                KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_HEADSETHOOK, true));
+        verify(mMockCallsManager, times(1)).onMediaButton(HeadsetMediaButton.LONG_PRESS);
+
+        mSessionCallback.onMediaButtonEvent(getKeyEventIntent(
+                KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HEADSETHOOK, false));
+        verify(mMockCallsManager, times(1)).onMediaButton(HeadsetMediaButton.LONG_PRESS);
+    }
+
+    @SmallTest
+    @Test
+    public void testMediaSessionWrapperSetActive() {
+        MediaSession session = Mockito.mock(MediaSession.class);
+        MediaSessionWrapper wrapper = mHeadsetMediaButton.new MediaSessionWrapper(session);
+
+        final boolean active = true;
+        wrapper.setActive(active);
+        verify(session).setActive(active);
+    }
+
+    @SmallTest
+    @Test
+    public void testMediaSessionWrapperSetCallback() {
+        MediaSession session = Mockito.mock(MediaSession.class);
+        MediaSessionWrapper wrapper = mHeadsetMediaButton.new MediaSessionWrapper(session);
+
+        wrapper.setCallback(mSessionCallback);
+        verify(session).setCallback(mSessionCallback);
+    }
+
+    @SmallTest
+    @Test
+    public void testMediaSessionWrapperIsActive() {
+        MediaSession session = Mockito.mock(MediaSession.class);
+        MediaSessionWrapper wrapper = mHeadsetMediaButton.new MediaSessionWrapper(session);
+
+        final boolean active = true;
+        when(session.isActive()).thenReturn(active);
+        assertEquals(active, wrapper.isActive());
     }
 
     /**
@@ -117,4 +306,15 @@
         when(regularCall.isExternalCall()).thenReturn(false);
         return regularCall;
     }
+
+    private Intent getKeyEventIntent(int action, int code, boolean longPress) {
+        KeyEvent e = new KeyEvent(action, code);
+        if (longPress) {
+            e = KeyEvent.changeFlags(e, KeyEvent.FLAG_LONG_PRESS);
+        }
+
+        Intent intent = new Intent();
+        intent.putExtra(Intent.EXTRA_KEY_EVENT, e);
+        return intent;
+    }
 }
diff --git a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
index 3d387a8..a08184b 100644
--- a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
+++ b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
@@ -35,6 +35,7 @@
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -73,6 +74,7 @@
 import android.os.Looper;
 import android.os.Process;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.permission.PermissionCheckerManager;
 import android.telecom.CallAudioState;
 import android.telecom.InCallService;
@@ -88,6 +90,7 @@
 import com.android.internal.telecom.IInCallAdapter;
 import com.android.internal.telecom.IInCallService;
 import com.android.server.telecom.Analytics;
+import com.android.server.telecom.AnomalyReporterAdapter;
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.CarModeTracker;
@@ -95,6 +98,7 @@
 import com.android.server.telecom.DefaultDialerCache;
 import com.android.server.telecom.EmergencyCallHelper;
 import com.android.server.telecom.InCallController;
+import com.android.server.telecom.ParcelableCallUtils;
 import com.android.server.telecom.PhoneAccountRegistrar;
 import com.android.server.telecom.R;
 import com.android.server.telecom.RoleManagerAdapter;
@@ -112,6 +116,7 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.ArgumentMatcher;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.MockitoSession;
 import org.mockito.invocation.InvocationOnMock;
@@ -144,6 +149,9 @@
     @Mock Analytics.CallInfoImpl mCallInfo;
     @Mock NotificationManager mNotificationManager;
     @Mock PermissionInfo mMockPermissionInfo;
+    @Mock InCallController.InCallServiceInfo mInCallServiceInfo;
+    @Mock private AnomalyReporterAdapter mAnomalyReporterAdapter;
+    @Mock UserManager mMockUserManager;
 
     @Rule
     public TestRule compatChangeRule = new PlatformCompatChangeRule();
@@ -172,7 +180,9 @@
     private static final int APPOP_NONUI_UID = 7;
 
     private static final PhoneAccountHandle PA_HANDLE =
-            new PhoneAccountHandle(new ComponentName("pa_pkg", "pa_cls"), "pa_id");
+            new PhoneAccountHandle(new ComponentName("pa_pkg", "pa_cls"),
+                    "pa_id_0", UserHandle.of(CURRENT_USER_ID));
+    private static final UserHandle DUMMY_USER_HANDLE = UserHandle.of(10);
 
     private UserHandle mUserHandle = UserHandle.of(CURRENT_USER_ID);
     private InCallController mInCallController;
@@ -191,6 +201,7 @@
         super.setUp();
         MockitoAnnotations.initMocks(this);
         when(mMockCall.getAnalytics()).thenReturn(new Analytics.CallInfo());
+        when(mMockCall.getUserHandleFromTargetPhoneAccount()).thenReturn(mUserHandle);
         doReturn(mMockResources).when(mMockContext).getResources();
         doReturn(mMockAppOpsManager).when(mMockContext).getSystemService(AppOpsManager.class);
         doReturn(SYS_PKG).when(mMockResources).getString(
@@ -285,6 +296,15 @@
 
     @SmallTest
     @Test
+    public void testBringToForeground_NoInCallServices() {
+        // verify that there is not any bound InCallServices for the user requesting for foreground
+        assertFalse(mInCallController.getInCallServices().containsKey(mUserHandle));
+        // ensure that the method behaves properly on invocation
+        mInCallController.bringToForeground(true /* showDialPad */, mUserHandle /* callingUser */);
+    }
+
+    @SmallTest
+    @Test
     public void testCarModeAppRemoval() {
         setupMockPackageManager(true /* default */, true /* system */, true /* external calls */);
         when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
@@ -348,6 +368,7 @@
         when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
         when(mMockCall.isIncoming()).thenReturn(true);
         when(mMockCall.isExternalCall()).thenReturn(false);
+        when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
         when(mTimeoutsAdapter.getEmergencyCallbackWindowMillis(any(ContentResolver.class)))
                 .thenReturn(300_000L);
 
@@ -359,7 +380,7 @@
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
 
         Intent bindIntent = bindIntentCaptor.getValue();
         assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
@@ -393,7 +414,7 @@
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
 
         Intent bindIntent = bindIntentCaptor.getValue();
         assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
@@ -421,7 +442,7 @@
         when(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID))
                 .thenReturn(DEF_PKG);
         when(mMockContext.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class),
-                anyInt(), eq(UserHandle.CURRENT))).thenReturn(true);
+                anyInt(), eq(mUserHandle))).thenReturn(true);
 
         setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
         mInCallController.bindToServices(mMockCall);
@@ -445,7 +466,7 @@
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
 
         Intent bindIntent = bindIntentCaptor.getValue();
         assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
@@ -467,6 +488,9 @@
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockCallsManager.isInEmergencyCall()).thenReturn(true);
         when(mMockCall.isEmergencyCall()).thenReturn(true);
+        when(mMockContext.getSystemService(eq(UserManager.class)))
+            .thenReturn(mMockUserManager);
+        when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(false);
         when(mMockCall.isIncoming()).thenReturn(false);
         when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
         when(mMockCall.getIntentExtras()).thenReturn(callExtras);
@@ -475,7 +499,7 @@
                 .thenReturn(DEF_PKG);
         when(mMockContext.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT))).thenReturn(true);
+                eq(mUserHandle))).thenReturn(true);
         when(mTimeoutsAdapter.getEmergencyCallbackWindowMillis(any(ContentResolver.class)))
                 .thenReturn(300_000L);
 
@@ -503,7 +527,7 @@
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
 
         Intent bindIntent = bindIntentCaptor.getValue();
         assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
@@ -526,6 +550,60 @@
                 eq(Manifest.permission.ACCESS_FINE_LOCATION), eq(mUserHandle));
     }
 
+    @MediumTest
+    @Test
+    public void
+    testBindToService_UserAssociatedWithCallIsInQuietMode_EmergCallInCallUi_BindsToPrimaryUser()
+        throws Exception {
+        when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
+        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+        when(mMockCall.isEmergencyCall()).thenReturn(true);
+        when(mMockCall.getUserHandleFromTargetPhoneAccount()).thenReturn(DUMMY_USER_HANDLE);
+        when(mMockContext.getSystemService(eq(UserManager.class)))
+            .thenReturn(mMockUserManager);
+        when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(true);
+        setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
+        setupMockPackageManagerLocationPermission(SYS_PKG, false /* granted */);
+
+        mInCallController.bindToServices(mMockCall);
+
+        ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mMockContext, times(1)).bindServiceAsUser(
+            bindIntentCaptor.capture(),
+            any(ServiceConnection.class),
+            eq(serviceBindingFlags),
+            eq(mUserHandle));
+        Intent bindIntent = bindIntentCaptor.getValue();
+        assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
+    }
+
+    @MediumTest
+    @Test
+    public void
+    testBindToService_UserAssociatedWithCallNotInQuietMode_EmergCallInCallUi_BindsToAssociatedUser()
+        throws Exception {
+        when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
+        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+        when(mMockCall.isEmergencyCall()).thenReturn(true);
+        when(mMockCall.getUserHandleFromTargetPhoneAccount()).thenReturn(DUMMY_USER_HANDLE);
+        when(mMockContext.getSystemService(eq(UserManager.class)))
+            .thenReturn(mMockUserManager);
+        when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(false);
+        setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
+        setupMockPackageManagerLocationPermission(SYS_PKG, false /* granted */);
+
+        mInCallController.bindToServices(mMockCall);
+
+        ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mMockContext, times(1)).bindServiceAsUser(
+            bindIntentCaptor.capture(),
+            any(ServiceConnection.class),
+            eq(serviceBindingFlags),
+            eq(DUMMY_USER_HANDLE));
+        Intent bindIntent = bindIntentCaptor.getValue();
+        assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
+    }
+
     /**
      * This test verifies the behavior of Telecom when the system dialer crashes on binding and must
      * be restarted.  Specifically, it ensures when the system dialer crashes we revoke the runtime
@@ -542,6 +620,9 @@
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockCallsManager.isInEmergencyCall()).thenReturn(true);
         when(mMockCall.isEmergencyCall()).thenReturn(true);
+        when(mMockContext.getSystemService(eq(UserManager.class)))
+            .thenReturn(mMockUserManager);
+        when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(false);
         when(mMockCall.isIncoming()).thenReturn(false);
         when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
         when(mMockCall.getIntentExtras()).thenReturn(callExtras);
@@ -552,7 +633,7 @@
                 ArgumentCaptor.forClass(ServiceConnection.class);
         when(mMockContext.bindServiceAsUser(any(Intent.class), serviceConnectionCaptor.capture(),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT))).thenReturn(true);
+                eq(mUserHandle))).thenReturn(true);
         when(mTimeoutsAdapter.getEmergencyCallbackWindowMillis(any(ContentResolver.class)))
                 .thenReturn(300_000L);
 
@@ -580,7 +661,7 @@
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
 
         Intent bindIntent = bindIntentCaptor.getValue();
         assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
@@ -609,7 +690,7 @@
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
 
         // Verify we were re-granted the runtime permission.
         verify(mMockPackageManager, times(2)).grantRuntimePermission(eq(SYS_PKG),
@@ -663,7 +744,7 @@
                 bindIntentCaptor.capture(),
                 serviceConnectionCaptor.capture(),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
 
         Intent bindIntent = bindIntentCaptor.getValue();
         assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
@@ -695,7 +776,7 @@
                 bindIntentCaptor2.capture(),
                 any(ServiceConnection.class),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
 
         bindIntent = bindIntentCaptor2.getValue();
         assertEquals(SYS_PKG, bindIntent.getComponent().getPackageName());
@@ -710,6 +791,7 @@
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockCall.isIncoming()).thenReturn(false);
         when(mMockCall.isExternalCall()).thenReturn(false);
+        when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
         when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
         when(mMockCall.getAnalytics()).thenReturn(mCallInfo);
         when(mMockContext.bindServiceAsUser(
@@ -741,8 +823,9 @@
         // verify(mockInCallService).setInCallAdapter(any(IInCallAdapter.class));
         serviceConnection.onNullBinding(defDialerComponentName);
 
-        verify(mNotificationManager).notify(eq(NOTIFICATION_TAG),
-                eq(IN_CALL_SERVICE_NOTIFICATION_ID), any(Notification.class));
+        verify(mNotificationManager).notifyAsUser(eq(NOTIFICATION_TAG),
+                eq(IN_CALL_SERVICE_NOTIFICATION_ID), any(Notification.class),
+                eq(mUserHandle));
         verify(mCallInfo).addInCallService(eq(defDialerComponentName.flattenToShortString()),
                 anyInt(), anyLong(), eq(true));
 
@@ -751,10 +834,50 @@
                 bindIntentCaptor2.capture(),
                 any(ServiceConnection.class),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
         assertEquals(sysDialerComponentName, bindIntentCaptor2.getValue().getComponent());
     }
 
+    @Test
+    public void testBindToService_CarModeUI_Crash() throws Exception {
+        setupMocks(false /* isExternalCall */);
+        setupMockPackageManager(true /* default */, true /* system */, true /* external calls */);
+
+        // Enable car mode
+        when(mMockSystemStateHelper.isCarModeOrProjectionActive()).thenReturn(true);
+        mInCallController.handleCarModeChange(UiModeManager.DEFAULT_PRIORITY, CAR_PKG, true);
+
+        // Now bind; we should only bind to one app.
+        mInCallController.bindToServices(mMockCall);
+
+        // Bind InCallServices
+        ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        ArgumentCaptor<ServiceConnection> serviceConnectionCaptor =
+                ArgumentCaptor.forClass(ServiceConnection.class);
+        verify(mMockContext, times(1)).bindServiceAsUser(
+                bindIntentCaptor.capture(),
+                serviceConnectionCaptor.capture(),
+                eq(serviceBindingFlags),
+                eq(mUserHandle));
+
+        // Verify bind car mode ui
+        assertEquals(1, bindIntentCaptor.getAllValues().size());
+        verifyBinding(bindIntentCaptor, 0, CAR_PKG, CAR_CLASS);
+
+        // Emulate a crash in the CarModeUI
+        ServiceConnection serviceConnection = serviceConnectionCaptor.getValue();
+        serviceConnection.onServiceDisconnected(bindIntentCaptor.getValue().getComponent());
+
+        ArgumentCaptor<Intent> bindIntentCaptor2 = ArgumentCaptor.forClass(Intent.class);
+        verify(mMockContext, times(2)).bindServiceAsUser(
+                bindIntentCaptor2.capture(),
+                any(ServiceConnection.class),
+                eq(serviceBindingFlags),
+                eq(mUserHandle));
+
+        verifyBinding(bindIntentCaptor2, 1, CAR_PKG, CAR_CLASS);
+    }
+
     /**
      * Ensures that the {@link InCallController} will bind to an {@link InCallService} which
      * supports external calls.
@@ -785,7 +908,7 @@
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
 
         Intent bindIntent = bindIntentCaptor.getValue();
         assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
@@ -805,6 +928,7 @@
         when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
         when(mMockCall.isIncoming()).thenReturn(true);
         when(mMockCall.isExternalCall()).thenReturn(false);
+        when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
         when(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID)).thenReturn(DEF_PKG);
         when(mMockContext.bindServiceAsUser(nullable(Intent.class),
                 nullable(ServiceConnection.class), anyInt(), nullable(UserHandle.class)))
@@ -823,7 +947,7 @@
                 bindIntentCaptor.capture(),
                 serviceConnectionCaptor.capture(),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
 
         // Pretend that the call has gone away.
         when(mMockCallsManager.getCalls()).thenReturn(Collections.emptyList());
@@ -871,7 +995,7 @@
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
         // Verify bind car mode ui
         assertEquals(1, bindIntentCaptor.getAllValues().size());
         verifyBinding(bindIntentCaptor, 0, CAR_PKG, CAR_CLASS);
@@ -899,7 +1023,7 @@
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
         // Verify bind to default package, instead of the invalid car mode ui.
         assertEquals(1, bindIntentCaptor.getAllValues().size());
         verifyBinding(bindIntentCaptor, 0, DEF_PKG, DEF_CLASS);
@@ -942,7 +1066,7 @@
                     bindIntentCaptor.capture(),
                     any(ServiceConnection.class),
                     eq(serviceBindingFlags),
-                    eq(UserHandle.CURRENT));
+                    eq(mUserHandle));
 
             // Verify bind
             assertEquals(2, bindIntentCaptor.getAllValues().size());
@@ -986,7 +1110,7 @@
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
 
         // Verify bind
         assertEquals(1, bindIntentCaptor.getAllValues().size());
@@ -995,8 +1119,10 @@
         verifyBinding(bindIntentCaptor, 0, NONUI_PKG, NONUI_CLASS);
 
         // Verify notification is not sent by NotificationManager
-        verify(mNotificationManager, times(0)).notify(eq(InCallController.NOTIFICATION_TAG),
-                eq(InCallController.IN_CALL_SERVICE_NOTIFICATION_ID), any());
+        verify(mNotificationManager, times(0)).notifyAsUser(
+                eq(InCallController.NOTIFICATION_TAG),
+                eq(InCallController.IN_CALL_SERVICE_NOTIFICATION_ID), any(),
+                eq(mUserHandle));
     }
 
     @MediumTest
@@ -1019,7 +1145,7 @@
                 bindIntentCaptor.capture(),
                 serviceConnectionCaptor.capture(),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
         assertEquals(1, bindIntentCaptor.getAllValues().size());
         verifyBinding(bindIntentCaptor, 0, DEF_PKG, DEF_CLASS);
 
@@ -1057,7 +1183,7 @@
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
     }
 
     /**
@@ -1088,7 +1214,7 @@
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
         // Verify bind car mode ui
         assertEquals(4, bindIntentCaptor.getAllValues().size());
 
@@ -1125,6 +1251,7 @@
         when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
         when(mMockCall.isIncoming()).thenReturn(false);
         when(mMockCall.isExternalCall()).thenReturn(false);
+        when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
         when(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID)).thenReturn(DEF_PKG);
         when(mMockContext.bindServiceAsUser(nullable(Intent.class),
                 nullable(ServiceConnection.class), anyInt(), nullable(UserHandle.class)))
@@ -1145,7 +1272,7 @@
                 bindIntentCaptor.capture(),
                 serviceConnectionCaptor.capture(),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
 
         CompletableFuture<Boolean> bindTimeout = mInCallController.getBindingFuture();
 
@@ -1213,7 +1340,7 @@
                 any(Intent.class),
                 any(ServiceConnection.class),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
 
         // Now switch to car mode.
         // Enable car mode and enter car mode at default priority.
@@ -1225,7 +1352,7 @@
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
         // Verify bind car mode ui
         assertEquals(1, bindIntentCaptor.getAllValues().size());
         verifyBinding(bindIntentCaptor, 0, CAR_PKG, CAR_CLASS);
@@ -1251,7 +1378,7 @@
                 any(Intent.class),
                 any(ServiceConnection.class),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
 
         // Now switch to car mode.
         // Enable car mode and enter car mode at default priority.
@@ -1268,7 +1395,7 @@
                 any(Intent.class),
                 serviceConnectionCaptor.capture(),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
 
         ServiceConnection serviceConnection = serviceConnectionCaptor.getValue();
         ComponentName defDialerComponentName = new ComponentName(DEF_PKG, DEF_CLASS);
@@ -1284,6 +1411,59 @@
         verify(mockInCallService, never()).addCall(any(ParcelableCall.class));
     }
 
+    @Test
+    public void testSanitizeDndExtraFromParcelableCall() throws Exception {
+        setupMocks(false /* isExternalCall */);
+        setupMockPackageManager(true /* default */, true /* system */, true /* external calls */);
+        when(mMockPackageManager.checkPermission(
+                matches(Manifest.permission.READ_CONTACTS),
+                matches(DEF_PKG))).thenReturn(PackageManager.PERMISSION_DENIED);
+
+        when(mMockCall.getExtras()).thenReturn(null);
+        ParcelableCall parcelableCallNullExtras = Mockito.spy(
+                ParcelableCallUtils.toParcelableCall(mMockCall,
+                        false /* includevideoProvider */,
+                        null /* phoneAccountRegistrar */,
+                        false /* supportsExternalCalls */,
+                        false /* includeRttCall */,
+                        false /* isForSystemDialer */));
+
+        when(parcelableCallNullExtras.getExtras()).thenReturn(null);
+        assertNull(parcelableCallNullExtras.getExtras());
+        when(mInCallServiceInfo.getComponentName())
+                .thenReturn(new ComponentName(DEF_PKG, DEF_CLASS));
+        // ensure sanitizeParcelableCallForService does not hit a NPE when Null extras are provided
+        mInCallController.sanitizeParcelableCallForService(mInCallServiceInfo,
+                parcelableCallNullExtras);
+
+
+        Bundle extras = new Bundle();
+        extras.putBoolean(android.telecom.Call.EXTRA_IS_SUPPRESSED_BY_DO_NOT_DISTURB, true);
+        when(mMockCall.getExtras()).thenReturn(extras);
+
+        ParcelableCall parcelableCallWithExtras = ParcelableCallUtils.toParcelableCall(mMockCall,
+                false /* includevideoProvider */,
+                null /* phoneAccountRegistrar */,
+                false /* supportsExternalCalls */,
+                false /* includeRttCall */,
+                false /* isForSystemDialer */);
+
+        // ensure sanitizeParcelableCallForService sanitizes the
+        // EXTRA_IS_SUPPRESSED_BY_DO_NOT_DISTURB from a ParcelableCall
+        // w/o  Manifest.permission.READ_CONTACTS
+        ParcelableCall sanitizedCall =
+                mInCallController.sanitizeParcelableCallForService(mInCallServiceInfo,
+                        parcelableCallWithExtras);
+
+        // sanitized call should not have the extra
+        assertFalse(sanitizedCall.getExtras().containsKey(
+                android.telecom.Call.EXTRA_IS_SUPPRESSED_BY_DO_NOT_DISTURB));
+
+        // root ParcelableCall should still have the extra
+        assertTrue(parcelableCallWithExtras.getExtras().containsKey(
+                android.telecom.Call.EXTRA_IS_SUPPRESSED_BY_DO_NOT_DISTURB));
+    }
+
     private void setupMocks(boolean isExternalCall) {
         setupMocks(isExternalCall, false /* isSelfManagedCall */);
     }
@@ -1296,7 +1476,7 @@
         when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
         when(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID)).thenReturn(DEF_PKG);
         when(mMockContext.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class),
-                anyInt(), eq(UserHandle.CURRENT))).thenReturn(true);
+                anyInt(), any(UserHandle.class))).thenReturn(true);
         when(mMockCall.isExternalCall()).thenReturn(isExternalCall);
         when(mMockCall.isSelfManaged()).thenReturn(isSelfManagedCall);
         when(mMockCall.visibleToInCallService()).thenReturn(isSelfManagedCall);
@@ -1487,7 +1667,7 @@
                 return resolveInfo;
             }
         }).when(mMockPackageManager).queryIntentServicesAsUser(
-                any(Intent.class), anyInt(), eq(CURRENT_USER_ID));
+                any(Intent.class), anyInt(), anyInt());
 
         if (useDefaultDialer) {
             when(mMockPackageManager
diff --git a/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java b/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java
index d114cb8..88b5bb5 100644
--- a/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java
+++ b/tests/src/com/android/server/telecom/tests/InCallServiceFixture.java
@@ -26,9 +26,11 @@
 import android.os.IInterface;
 import android.os.RemoteException;
 import android.telecom.CallAudioState;
+import android.telecom.CallEndpoint;
 import android.telecom.ParcelableCall;
 
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -105,6 +107,15 @@
         }
 
         @Override
+        public void onCallEndpointChanged(CallEndpoint callEndpoint) {}
+
+        @Override
+        public void onAvailableCallEndpointsChanged(List<CallEndpoint> availableCallEndpoints) {}
+
+        @Override
+        public void onMuteStateChanged(boolean isMuted) {}
+
+        @Override
         public void bringToForeground(boolean showDialpad) throws RemoteException {
             mBringToForeground = true;
             mShowDialpad = showDialpad;
diff --git a/tests/src/com/android/server/telecom/tests/InCallTonePlayerTest.java b/tests/src/com/android/server/telecom/tests/InCallTonePlayerTest.java
index eadda0d..f11afc1 100644
--- a/tests/src/com/android/server/telecom/tests/InCallTonePlayerTest.java
+++ b/tests/src/com/android/server/telecom/tests/InCallTonePlayerTest.java
@@ -140,13 +140,16 @@
 
     @SmallTest
     @Test
-    public void testNoEndCallToneInSilence() {
+    public void testEndCallTonePlaysWhenRingIsSilent() {
         when(mAudioManagerAdapter.isVolumeOverZero()).thenReturn(false);
-        assertFalse(mInCallTonePlayer.startTone());
+        assertTrue(mInCallTonePlayer.startTone());
+        // Verify we did play a tone.
+        verify(mMediaPlayerFactory, timeout(TEST_TIMEOUT)).get(anyInt(), any());
+        verify(mCallAudioManager).setIsTonePlaying(eq(true));
 
-        // Verify we didn't play a tone.
-        verify(mCallAudioManager, never()).setIsTonePlaying(eq(true));
-        verify(mMediaPlayerFactory, never()).get(anyInt(), any());
+        mInCallTonePlayer.stopTone();
+        // Timeouts due to threads!
+        verify(mCallAudioManager, timeout(TEST_TIMEOUT)).setIsTonePlaying(eq(false));
     }
 
     @SmallTest
diff --git a/tests/src/com/android/server/telecom/tests/IncomingCallNotifierTest.java b/tests/src/com/android/server/telecom/tests/IncomingCallNotifierTest.java
index a871b73..a38de94 100644
--- a/tests/src/com/android/server/telecom/tests/IncomingCallNotifierTest.java
+++ b/tests/src/com/android/server/telecom/tests/IncomingCallNotifierTest.java
@@ -17,9 +17,12 @@
 package com.android.server.telecom.tests;
 
 import android.app.NotificationManager;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.os.Build;
+import android.os.UserHandle;
+import android.telecom.PhoneAccountHandle;
 import android.telecom.VideoProfile;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -72,6 +75,8 @@
 
         when(mAudioCall.getVideoState()).thenReturn(VideoProfile.STATE_AUDIO_ONLY);
         when(mAudioCall.getTargetPhoneAccountLabel()).thenReturn("Bar");
+        when(mAudioCall.getUserHandleFromTargetPhoneAccount()).
+                thenReturn(UserHandle.CURRENT);
         when(mVideoCall.getVideoState()).thenReturn(VideoProfile.STATE_BIDIRECTIONAL);
         when(mVideoCall.getTargetPhoneAccountLabel()).thenReturn("Bar");
         when(mRingingCall.isSelfManaged()).thenReturn(true);
@@ -79,6 +84,8 @@
         when(mRingingCall.getState()).thenReturn(CallState.RINGING);
         when(mRingingCall.getVideoState()).thenReturn(VideoProfile.STATE_AUDIO_ONLY);
         when(mRingingCall.getTargetPhoneAccountLabel()).thenReturn("Foo");
+        when(mRingingCall.getUserHandleFromTargetPhoneAccount()).
+                thenReturn(UserHandle.CURRENT);
         when(mRingingCall.getHandoverState()).thenReturn(HandoverState.HANDOVER_NONE);
     }
 
@@ -95,8 +102,10 @@
     @Test
     public void testSingleCall() {
         mIncomingCallNotifier.onCallAdded(mAudioCall);
-        verify(mNotificationManager, never()).notify(eq(IncomingCallNotifier.NOTIFICATION_TAG),
-                eq(IncomingCallNotifier.NOTIFICATION_INCOMING_CALL), any());
+        verify(mNotificationManager, never()).notifyAsUser(
+                eq(IncomingCallNotifier.NOTIFICATION_TAG),
+                eq(IncomingCallNotifier.NOTIFICATION_INCOMING_CALL), any(),
+                eq(UserHandle.CURRENT));
     }
 
     /**
@@ -107,8 +116,10 @@
     public void testIncomingDuringOngoingCall() {
         when(mCallsManagerProxy.hasUnholdableCallsForOtherConnectionService(any())).thenReturn(false);
         mIncomingCallNotifier.onCallAdded(mRingingCall);
-        verify(mNotificationManager, never()).notify(eq(IncomingCallNotifier.NOTIFICATION_TAG),
-                eq(IncomingCallNotifier.NOTIFICATION_INCOMING_CALL), any());
+        verify(mNotificationManager, never()).notifyAsUser(
+                eq(IncomingCallNotifier.NOTIFICATION_TAG),
+                eq(IncomingCallNotifier.NOTIFICATION_INCOMING_CALL), any(),
+                eq(UserHandle.CURRENT));
     }
 
     /**
@@ -123,8 +134,10 @@
 
         mIncomingCallNotifier.onCallAdded(mAudioCall);
         mIncomingCallNotifier.onCallAdded(mRingingCall);
-        verify(mNotificationManager, never()).notify(eq(IncomingCallNotifier.NOTIFICATION_TAG),
-                eq(IncomingCallNotifier.NOTIFICATION_INCOMING_CALL), any());;
+        verify(mNotificationManager, never()).notifyAsUser(
+                eq(IncomingCallNotifier.NOTIFICATION_TAG),
+                eq(IncomingCallNotifier.NOTIFICATION_INCOMING_CALL), any(),
+                eq(UserHandle.CURRENT));
     }
 
     /**
@@ -139,11 +152,13 @@
 
         mIncomingCallNotifier.onCallAdded(mAudioCall);
         mIncomingCallNotifier.onCallAdded(mRingingCall);
-        verify(mNotificationManager).notify(eq(IncomingCallNotifier.NOTIFICATION_TAG),
-                eq(IncomingCallNotifier.NOTIFICATION_INCOMING_CALL), any());
+        verify(mNotificationManager).notifyAsUser(
+                eq(IncomingCallNotifier.NOTIFICATION_TAG),
+                eq(IncomingCallNotifier.NOTIFICATION_INCOMING_CALL), any(),
+                eq(UserHandle.CURRENT));
         mIncomingCallNotifier.onCallRemoved(mRingingCall);
-        verify(mNotificationManager).cancel(eq(IncomingCallNotifier.NOTIFICATION_TAG),
-                eq(IncomingCallNotifier.NOTIFICATION_INCOMING_CALL));
+        verify(mNotificationManager).cancelAsUser(eq(IncomingCallNotifier.NOTIFICATION_TAG),
+                eq(IncomingCallNotifier.NOTIFICATION_INCOMING_CALL), eq(UserHandle.CURRENT));
     }
 
     /**
@@ -161,8 +176,10 @@
         mIncomingCallNotifier.onCallAdded(mRingingCall);
 
         // Incoming call is in the middle of a handover, don't expect to be notified.
-        verify(mNotificationManager, never()).notify(eq(IncomingCallNotifier.NOTIFICATION_TAG),
-                eq(IncomingCallNotifier.NOTIFICATION_INCOMING_CALL), any());;
+        verify(mNotificationManager, never()).notifyAsUser(
+                eq(IncomingCallNotifier.NOTIFICATION_TAG),
+                eq(IncomingCallNotifier.NOTIFICATION_INCOMING_CALL), any(),
+                eq(UserHandle.CURRENT));
     }
 
     /**
@@ -180,7 +197,9 @@
         mIncomingCallNotifier.onCallAdded(mRingingCall);
 
         // Incoming call is done a handover, don't expect to be notified.
-        verify(mNotificationManager, never()).notify(eq(IncomingCallNotifier.NOTIFICATION_TAG),
-                eq(IncomingCallNotifier.NOTIFICATION_INCOMING_CALL), any());;
+        verify(mNotificationManager, never()).notifyAsUser(
+                eq(IncomingCallNotifier.NOTIFICATION_TAG),
+                eq(IncomingCallNotifier.NOTIFICATION_INCOMING_CALL), any(),
+                eq(UserHandle.CURRENT));
     }
 }
diff --git a/tests/src/com/android/server/telecom/tests/MissedCallNotifierTest.java b/tests/src/com/android/server/telecom/tests/MissedCallNotifierTest.java
new file mode 100644
index 0000000..e441835
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/MissedCallNotifierTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2022 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.server.telecom.tests;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.telecom.PhoneAccountHandle;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.telecom.CallerInfo;
+
+import com.android.server.telecom.MissedCallNotifier;
+import com.android.server.telecom.MissedCallNotifier.CallInfo;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+@RunWith(JUnit4.class)
+public class MissedCallNotifierTest extends TelecomTestCase {
+    private static final ComponentName COMPONENT_NAME =
+            new ComponentName("com.anything", "com.whatever");
+    private static final Uri TEL_CALL_HANDLE = Uri.parse("tel:+11915552620");
+    private static final long CALL_TIMESTAMP = 1;
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @Override
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @SmallTest
+    @Test
+    public void testCallInfoFactory() {
+        final CallerInfo callerInfo = new CallerInfo();
+        final String phoneNumber = "1111";
+        final String name = "name";
+        callerInfo.setPhoneNumber(phoneNumber);
+        callerInfo.setName(name);
+        final PhoneAccountHandle phoneAccountHandle = new PhoneAccountHandle(COMPONENT_NAME, "id");
+
+        MissedCallNotifier.CallInfo callInfo = new MissedCallNotifier.CallInfoFactory()
+                .makeCallInfo(callerInfo, phoneAccountHandle, TEL_CALL_HANDLE, CALL_TIMESTAMP);
+
+        assertEquals(callerInfo, callInfo.getCallerInfo());
+        assertEquals(phoneAccountHandle, callInfo.getPhoneAccountHandle());
+        assertEquals(TEL_CALL_HANDLE, callInfo.getHandle());
+        assertEquals(TEL_CALL_HANDLE.getSchemeSpecificPart(),
+                callInfo.getHandleSchemeSpecificPart());
+        assertEquals(CALL_TIMESTAMP, callInfo.getCreationTimeMillis());
+        assertEquals(phoneNumber, callInfo.getPhoneNumber());
+        assertEquals(name, callInfo.getName());
+    }
+
+    @SmallTest
+    @Test
+    public void testCallInfoFactoryNullParam() {
+        MissedCallNotifier.CallInfo callInfo = new MissedCallNotifier.CallInfoFactory()
+                .makeCallInfo(null, null, null, CALL_TIMESTAMP);
+
+        assertNull(callInfo.getCallerInfo());
+        assertNull(callInfo.getPhoneAccountHandle());
+        assertNull(callInfo.getHandle());
+        assertNull(callInfo.getHandleSchemeSpecificPart());
+        assertEquals(CALL_TIMESTAMP, callInfo.getCreationTimeMillis());
+        assertNull(callInfo.getPhoneNumber());
+        assertNull(callInfo.getName());
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/MissedInformationTest.java b/tests/src/com/android/server/telecom/tests/MissedInformationTest.java
index f2f0cd8..8ea2739 100644
--- a/tests/src/com/android/server/telecom/tests/MissedInformationTest.java
+++ b/tests/src/com/android/server/telecom/tests/MissedInformationTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.telecom.tests;
 
+import static android.Manifest.permission.MODIFY_PHONE_STATE;
 import static android.provider.CallLog.Calls.AUTO_MISSED_EMERGENCY_CALL;
 import static android.provider.CallLog.Calls.AUTO_MISSED_MAXIMUM_DIALING;
 import static android.provider.CallLog.Calls.AUTO_MISSED_MAXIMUM_RINGING;
@@ -117,6 +118,8 @@
         mPackageManager = mContext.getPackageManager();
         when(mPackageManager.getPackageUid(anyString(), eq(0))).thenReturn(Binder.getCallingUid());
         mCountDownLatch  = new CountDownLatch(1);
+        doReturn(PackageManager.PERMISSION_GRANTED)
+                .when(mContext).checkCallingPermission(MODIFY_PHONE_STATE);
     }
 
     @Override
@@ -147,6 +150,8 @@
     public void testEmergencyCallPlacing() throws Exception {
         Analytics.dumpToParcelableAnalytics();
         setUpEmergencyCall();
+        when(mEmergencyCall.getUserHandleFromTargetPhoneAccount()).
+                thenReturn(mPhoneAccountA0.getAccountHandle().getUserHandle());
         mCallsManager.addCall(mEmergencyCall);
         assertTrue(mCallsManager.isInEmergencyCall());
 
@@ -354,6 +359,9 @@
         doReturn(mNotificationManager).when(mSpyContext)
                 .getSystemService(Context.NOTIFICATION_SERVICE);
         doReturn(false).when(mNotificationManager).matchesCallFilter(any(Bundle.class));
+        doReturn(false).when(mIncomingCall).wasDndCheckComputedForCall();
+        mCallsManager.getRinger().setNotificationManager(mNotificationManager);
+
         CallFilteringResult result = new CallFilteringResult.Builder()
                 .setShouldAllowCall(true)
                 .build();
diff --git a/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java b/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
index 2614abf..169aeb2 100644
--- a/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
+++ b/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
@@ -232,7 +232,7 @@
     public void testEmergencyCallWithNonDefaultDialer() {
         Uri handle = Uri.parse("tel:6505551911");
         doReturn(true).when(mComponentContextFixture.getTelephonyManager())
-                .isPotentialEmergencyNumber(eq(handle.getSchemeSpecificPart()));
+                .isEmergencyNumber(eq(handle.getSchemeSpecificPart()));
         Intent intent = new Intent(Intent.ACTION_CALL, handle);
 
         String ui_package_string = "sample_string_1";
@@ -303,7 +303,7 @@
     public void testActionEmergencyWithNonEmergencyNumber() {
         Uri handle = Uri.parse("tel:6505551911");
         doReturn(false).when(mComponentContextFixture.getTelephonyManager())
-                .isPotentialEmergencyNumber(eq(handle.getSchemeSpecificPart()));
+                .isEmergencyNumber(eq(handle.getSchemeSpecificPart()));
         Intent intent = new Intent(Intent.ACTION_CALL_EMERGENCY, handle);
         int result = processIntent(intent, true).disconnectCause;
 
@@ -317,7 +317,7 @@
         int videoState = VideoProfile.STATE_BIDIRECTIONAL;
         boolean isSpeakerphoneOn = true;
         doReturn(true).when(mComponentContextFixture.getTelephonyManager())
-                .isPotentialEmergencyNumber(eq(handle.getSchemeSpecificPart()));
+                .isEmergencyNumber(eq(handle.getSchemeSpecificPart()));
         intent.putExtra(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, isSpeakerphoneOn);
         intent.putExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, videoState);
 
@@ -439,7 +439,7 @@
         result.receiver.setResultData(newEmergencyNumber);
 
         doReturn(true).when(mComponentContextFixture.getTelephonyManager())
-                .isPotentialEmergencyNumber(eq(newEmergencyNumber));
+                .isEmergencyNumber(eq(newEmergencyNumber));
         result.receiver.onReceive(mContext, result.intent);
         verify(mCall).disconnect(eq(0L));
     }
diff --git a/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java b/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java
index a503283..fed8084 100644
--- a/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java
+++ b/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java
@@ -87,7 +87,7 @@
     @SmallTest
     @Test
     public void testParcelForNonSystemDialer() {
-        mCall.putExtras(Call.SOURCE_CONNECTION_SERVICE, getSomeExtras());
+        mCall.putConnectionServiceExtras(getSomeExtras());
         ParcelableCall call = ParcelableCallUtils.toParcelableCall(mCall,
                 false /* includevideoProvider */,
                 null /* phoneAccountRegistrar */,
@@ -105,7 +105,7 @@
     @SmallTest
     @Test
     public void testParcelForSystemDialer() {
-        mCall.putExtras(Call.SOURCE_CONNECTION_SERVICE, getSomeExtras());
+        mCall.putConnectionServiceExtras(getSomeExtras());
         ParcelableCall call = ParcelableCallUtils.toParcelableCall(mCall,
                 false /* includevideoProvider */,
                 null /* phoneAccountRegistrar */,
@@ -123,7 +123,7 @@
     @SmallTest
     @Test
     public void testParcelForSystemCallScreening() {
-        mCall.putExtras(Call.SOURCE_CONNECTION_SERVICE, getSomeExtras());
+        mCall.putConnectionServiceExtras(getSomeExtras());
         ParcelableCall call = ParcelableCallUtils.toParcelableCallForScreening(mCall,
                 true /* isPartOfSystemDialer */);
 
@@ -137,7 +137,7 @@
     @SmallTest
     @Test
     public void testParcelForSystemNonSystemCallScreening() {
-        mCall.putExtras(Call.SOURCE_CONNECTION_SERVICE, getSomeExtras());
+        mCall.putConnectionServiceExtras(getSomeExtras());
         ParcelableCall call = ParcelableCallUtils.toParcelableCallForScreening(mCall,
                 false /* isPartOfSystemDialer */);
 
diff --git a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
index ffa08e2..e573bb8 100644
--- a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
+++ b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
@@ -22,11 +22,15 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -83,22 +87,30 @@
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.UUID;
 
 @RunWith(JUnit4.class)
 public class PhoneAccountRegistrarTest extends TelecomTestCase {
 
     private static final int MAX_VERSION = Integer.MAX_VALUE;
+    private static final int INVALID_CHAR_LIMIT_COUNT =
+            PhoneAccountRegistrar.MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT + 1;
+    private static final String INVALID_STR = "a".repeat(INVALID_CHAR_LIMIT_COUNT);
     private static final String FILE_NAME = "phone-account-registrar-test-1223.xml";
     private static final String TEST_LABEL = "right";
+    private static final String TEST_ID = "123";
     private final String PACKAGE_1 = "PACKAGE_1";
     private final String PACKAGE_2 = "PACKAGE_2";
     private final String COMPONENT_NAME = "com.android.server.telecom.tests.MockConnectionService";
+    private final UserHandle USER_HANDLE_10 = new UserHandle(10);
     private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
     private PhoneAccountRegistrar mRegistrar;
     @Mock private SubscriptionManager mSubscriptionManager;
@@ -163,7 +175,7 @@
         testBundle.putString("EXTRA_STR1", "Hello");
         testBundle.putString("EXTRA_STR2", "There");
 
-        PhoneAccount input = makeQuickAccountBuilder("id0", 0)
+        PhoneAccount input = makeQuickAccountBuilder("id0", 0, null)
                 .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
                 .addSupportedUriScheme(PhoneAccount.SCHEME_VOICEMAIL)
                 .setExtras(testBundle)
@@ -271,7 +283,7 @@
         // Put in something valid so the bundle exists.
         testBundle.putString("EXTRA_OK", "OK");
 
-        PhoneAccount input = makeQuickAccountBuilder("id0", 0)
+        PhoneAccount input = makeQuickAccountBuilder("id0", 0, null)
                 .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
                 .addSupportedUriScheme(PhoneAccount.SCHEME_VOICEMAIL)
                 .setExtras(testBundle)
@@ -309,24 +321,33 @@
         mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
                 Mockito.mock(IConnectionService.class));
 
-        registerAndEnableAccount(makeQuickAccountBuilder("id" + i, i++)
+        registerAndEnableAccount(makeQuickAccountBuilder("id" + i, i++, null)
                 .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER
                         | PhoneAccount.CAPABILITY_CALL_PROVIDER)
                 .build());
-        registerAndEnableAccount(makeQuickAccountBuilder("id" + i, i++)
+        registerAndEnableAccount(makeQuickAccountBuilder("id" + i, i++, null)
                 .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER
                         | PhoneAccount.CAPABILITY_CALL_PROVIDER)
                 .build());
-        registerAndEnableAccount(makeQuickAccountBuilder("id" + i, i++)
+        registerAndEnableAccount(makeQuickAccountBuilder("id" + i, i++, null)
                 .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER
                         | PhoneAccount.CAPABILITY_CALL_PROVIDER)
                 .build());
-        registerAndEnableAccount(makeQuickAccountBuilder("id" + i, i++)
+        registerAndEnableAccount(makeQuickAccountBuilder("id" + i, i++, null)
+                .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER)
+                .build());
+        registerAndEnableAccount(makeQuickAccountBuilder("id" + i, i++, USER_HANDLE_10)
+                .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER
+                        | PhoneAccount.CAPABILITY_CALL_PROVIDER)
+                .build());
+        registerAndEnableAccount(makeQuickAccountBuilder("id" + i, i++, USER_HANDLE_10)
                 .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER)
                 .build());
 
-        assertEquals(4, mRegistrar.getAllPhoneAccountsOfCurrentUser().size());
-        assertEquals(3, mRegistrar.getCallCapablePhoneAccountsOfCurrentUser(null, false).size());
+        assertEquals(6, mRegistrar.
+                getAllPhoneAccounts(null, true).size());
+        assertEquals(4, mRegistrar.getCallCapablePhoneAccounts(null, false,
+                null, true).size());
         assertEquals(null, mRegistrar.getSimCallManagerOfCurrentUser());
         assertEquals(null, mRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(
                 PhoneAccount.SCHEME_TEL));
@@ -674,6 +695,75 @@
         assertEquals(TEST_LABEL, registeredAccount.getLabel());
     }
 
+    @MediumTest
+    @Test
+    public void testSecurityExceptionIsThrownWhenSelfManagedLacksPermissions() {
+        PhoneAccountHandle handle = makeQuickAccountHandle(
+                new ComponentName("self", "managed"), "selfie1");
+
+        PhoneAccount accountWithoutCapability = new PhoneAccount.Builder(handle, "label")
+                .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
+                .build();
+
+        assertFalse(mRegistrar.hasTransactionalCallCapabilities(accountWithoutCapability));
+
+        try {
+            mRegistrar.registerPhoneAccount(accountWithoutCapability);
+            fail("should not be able to register account");
+        } catch (SecurityException securityException) {
+            // test pass
+        } finally {
+            mRegistrar.unregisterPhoneAccount(handle);
+        }
+    }
+
+    @MediumTest
+    @Test
+    public void testSelfManagedPhoneAccountWithTransactionalOperations() {
+        PhoneAccountHandle handle = makeQuickAccountHandle(
+                new ComponentName("self", "managed"), "selfie1");
+
+        PhoneAccount accountWithCapability = new PhoneAccount.Builder(handle, "label")
+                .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED |
+                        PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS)
+                .build();
+
+        assertTrue(mRegistrar.hasTransactionalCallCapabilities(accountWithCapability));
+
+        try {
+            mRegistrar.registerPhoneAccount(accountWithCapability);
+            PhoneAccount registeredAccount = mRegistrar.getPhoneAccountUnchecked(handle);
+            assertEquals(TEST_LABEL, registeredAccount.getLabel().toString());
+        } finally {
+            mRegistrar.unregisterPhoneAccount(handle);
+        }
+    }
+
+    @MediumTest
+    @Test
+    public void testRegisterPhoneAccountAmendsSelfManagedCapabilityInternally() {
+        // GIVEN
+        PhoneAccountHandle handle = makeQuickAccountHandle(
+                new ComponentName("self", "managed"), "selfie1");
+        PhoneAccount accountWithCapability = new PhoneAccount.Builder(handle, "label")
+                .setCapabilities(
+                        PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS)
+                .build();
+
+        assertTrue(mRegistrar.hasTransactionalCallCapabilities(accountWithCapability));
+
+        try {
+            // WHEN
+            mRegistrar.registerPhoneAccount(accountWithCapability);
+            PhoneAccount registeredAccount = mRegistrar.getPhoneAccountUnchecked(handle);
+            // THEN
+            assertEquals(PhoneAccount.CAPABILITY_SELF_MANAGED, (registeredAccount.getCapabilities()
+                    & PhoneAccount.CAPABILITY_SELF_MANAGED));
+        } finally {
+            mRegistrar.unregisterPhoneAccount(handle);
+        }
+    }
+
     /**
      * Tests to ensure that when registering a self-managed PhoneAccount, it cannot also be defined
      * as a call provider, connection manager, or sim subscription.
@@ -727,7 +817,7 @@
         registerAndEnableAccount(nonSimAccount);
         registerAndEnableAccount(simAccount);
 
-        List<PhoneAccount> accounts = mRegistrar.getAllPhoneAccounts(Process.myUserHandle());
+        List<PhoneAccount> accounts = mRegistrar.getAllPhoneAccounts(Process.myUserHandle(), false);
         assertTrue(accounts.get(0).getLabel().toString().equals("2"));
         assertTrue(accounts.get(1).getLabel().toString().equals("1"));
     }
@@ -770,7 +860,7 @@
         registerAndEnableAccount(account2);
         registerAndEnableAccount(account1);
 
-        List<PhoneAccount> accounts = mRegistrar.getAllPhoneAccounts(Process.myUserHandle());
+        List<PhoneAccount> accounts = mRegistrar.getAllPhoneAccounts(Process.myUserHandle(), false);
         assertTrue(accounts.get(0).getLabel().toString().equals("c"));
         assertTrue(accounts.get(1).getLabel().toString().equals("b"));
         assertTrue(accounts.get(2).getLabel().toString().equals("a"));
@@ -808,7 +898,7 @@
         registerAndEnableAccount(account2);
         registerAndEnableAccount(account3);
 
-        List<PhoneAccount> accounts = mRegistrar.getAllPhoneAccounts(Process.myUserHandle());
+        List<PhoneAccount> accounts = mRegistrar.getAllPhoneAccounts(Process.myUserHandle(), false);
         assertTrue(accounts.get(0).getLabel().toString().equals("a"));
         assertTrue(accounts.get(1).getLabel().toString().equals("b"));
         assertTrue(accounts.get(2).getLabel().toString().equals("c"));
@@ -886,7 +976,7 @@
         registerAndEnableAccount(account5);
         registerAndEnableAccount(account6);
 
-        List<PhoneAccount> accounts = mRegistrar.getAllPhoneAccounts(Process.myUserHandle());
+        List<PhoneAccount> accounts = mRegistrar.getAllPhoneAccounts(Process.myUserHandle(), false);
         // Sim accts ordered by sort order first
         assertTrue(accounts.get(0).getLabel().toString().equals("z"));
         assertTrue(accounts.get(1).getLabel().toString().equals("y"));
@@ -910,14 +1000,14 @@
     public void testGetByEnabledState() throws Exception {
         mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
                 Mockito.mock(IConnectionService.class));
-        mRegistrar.registerPhoneAccount(makeQuickAccountBuilder("id1", 1)
+        mRegistrar.registerPhoneAccount(makeQuickAccountBuilder("id1", 1, null)
                 .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                 .build());
 
         assertEquals(0, mRegistrar.getCallCapablePhoneAccounts(PhoneAccount.SCHEME_TEL,
-                false /* includeDisabled */, Process.myUserHandle()).size());
+                false /* includeDisabled */, Process.myUserHandle(), false).size());
         assertEquals(1, mRegistrar.getCallCapablePhoneAccounts(PhoneAccount.SCHEME_TEL,
-                true /* includeDisabled */, Process.myUserHandle()).size());
+                true /* includeDisabled */, Process.myUserHandle(), false).size());
     }
 
     /**
@@ -930,21 +1020,21 @@
     public void testGetByScheme() throws Exception {
         mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
                 Mockito.mock(IConnectionService.class));
-        registerAndEnableAccount(makeQuickAccountBuilder("id1", 1)
+        registerAndEnableAccount(makeQuickAccountBuilder("id1", 1, null)
                 .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                 .setSupportedUriSchemes(Arrays.asList(PhoneAccount.SCHEME_SIP))
                 .build());
-        registerAndEnableAccount(makeQuickAccountBuilder("id2", 2)
+        registerAndEnableAccount(makeQuickAccountBuilder("id2", 2, null)
                 .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                 .setSupportedUriSchemes(Arrays.asList(PhoneAccount.SCHEME_TEL))
                 .build());
 
         assertEquals(1, mRegistrar.getCallCapablePhoneAccounts(PhoneAccount.SCHEME_SIP,
-                false /* includeDisabled */, Process.myUserHandle()).size());
+                false /* includeDisabled */, Process.myUserHandle(), false).size());
         assertEquals(1, mRegistrar.getCallCapablePhoneAccounts(PhoneAccount.SCHEME_TEL,
-                false /* includeDisabled */, Process.myUserHandle()).size());
+                false /* includeDisabled */, Process.myUserHandle(), false).size());
         assertEquals(2, mRegistrar.getCallCapablePhoneAccounts(null, false /* includeDisabled */,
-                Process.myUserHandle()).size());
+                Process.myUserHandle(), false).size());
     }
 
     /**
@@ -957,23 +1047,24 @@
     public void testGetByCapability() throws Exception {
         mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
                 Mockito.mock(IConnectionService.class));
-        registerAndEnableAccount(makeQuickAccountBuilder("id1", 1)
+        registerAndEnableAccount(makeQuickAccountBuilder("id1", 1, null)
                 .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER
                         | PhoneAccount.CAPABILITY_VIDEO_CALLING)
                 .setSupportedUriSchemes(Arrays.asList(PhoneAccount.SCHEME_SIP))
                 .build());
-        registerAndEnableAccount(makeQuickAccountBuilder("id2", 2)
+        registerAndEnableAccount(makeQuickAccountBuilder("id2", 2, null)
                 .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                 .setSupportedUriSchemes(Arrays.asList(PhoneAccount.SCHEME_SIP))
                 .build());
 
         assertEquals(1, mRegistrar.getCallCapablePhoneAccounts(PhoneAccount.SCHEME_SIP,
-                false /* includeDisabled */, Process.myUserHandle()).size(),
+                false /* includeDisabled */, Process.myUserHandle(), false).size(),
                 PhoneAccount.CAPABILITY_VIDEO_CALLING);
         assertEquals(2, mRegistrar.getCallCapablePhoneAccounts(PhoneAccount.SCHEME_SIP,
-                false /* includeDisabled */, Process.myUserHandle()).size(), 0 /* none extra */);
+                false /* includeDisabled */, Process.myUserHandle(), false)
+                .size(), 0 /* none extra */);
         assertEquals(0, mRegistrar.getCallCapablePhoneAccounts(PhoneAccount.SCHEME_SIP,
-                false /* includeDisabled */, Process.myUserHandle()).size(),
+                false /* includeDisabled */, Process.myUserHandle(), false).size(),
                 PhoneAccount.CAPABILITY_RTT);
     }
 
@@ -1059,8 +1150,8 @@
         registerAndEnableAccount(pa1);
         registerAndEnableAccount(pa2);
 
-        assertEquals(1, mRegistrar.getAllPhoneAccounts(users.get(0)).size());
-        assertEquals(1, mRegistrar.getAllPhoneAccounts(users.get(1)).size());
+        assertEquals(1, mRegistrar.getAllPhoneAccounts(users.get(0), false).size());
+        assertEquals(1, mRegistrar.getAllPhoneAccounts(users.get(1), false).size());
 
 
         // WHEN
@@ -1254,6 +1345,366 @@
                 defaultPhoneAccountHandle.phoneAccountHandle.getId());
     }
 
+    /**
+     * Test that an {@link IllegalArgumentException} is thrown when a package registers a
+     * {@link PhoneAccountHandle} with a { PhoneAccountHandle#packageName} that is over the
+     * character limit set
+     */
+    @Test
+    public void testInvalidPhoneAccountHandlePackageNameThrowsException() {
+        // GIVEN
+        String invalidPackageName = INVALID_STR;
+        PhoneAccountHandle handle = makeQuickAccountHandle(
+                new ComponentName(invalidPackageName, this.getClass().getName()), TEST_ID);
+        PhoneAccount.Builder builder = makeBuilderWithBindCapabilities(handle);
+
+        // THEN
+        try {
+            PhoneAccount account = builder.build();
+            assertEquals(invalidPackageName,
+                    account.getAccountHandle().getComponentName().getPackageName());
+            mRegistrar.registerPhoneAccount(account);
+            fail("failed to throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // pass test
+        } finally {
+            mRegistrar.unregisterPhoneAccount(handle);
+        }
+    }
+
+    /**
+     * Test that an {@link IllegalArgumentException} is thrown when a package registers a
+     * {@link PhoneAccountHandle} with a { PhoneAccountHandle#className} that is over the
+     * character limit set
+     */
+    @Test
+    public void testInvalidPhoneAccountHandleClassNameThrowsException() {
+        // GIVEN
+        String invalidClassName = INVALID_STR;
+        PhoneAccountHandle handle = makeQuickAccountHandle(
+                new ComponentName(this.getClass().getPackageName(), invalidClassName), TEST_ID);
+        PhoneAccount.Builder builder = makeBuilderWithBindCapabilities(handle);
+
+        // THEN
+        try {
+            PhoneAccount account = builder.build();
+            assertEquals(invalidClassName,
+                    account.getAccountHandle().getComponentName().getClassName());
+            mRegistrar.registerPhoneAccount(account);
+            fail("failed to throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // pass test
+        } finally {
+            mRegistrar.unregisterPhoneAccount(handle);
+        }
+    }
+
+    /**
+     * Test that an {@link IllegalArgumentException} is thrown when a package registers a
+     * {@link PhoneAccountHandle} with a { PhoneAccount#mId} that is over the character limit set
+     */
+    @Test
+    public void testInvalidPhoneAccountHandleIdThrowsException() {
+        // GIVEN
+        String invalidId = INVALID_STR;
+        PhoneAccountHandle handle = makeQuickAccountHandle(invalidId);
+        PhoneAccount.Builder builder = makeBuilderWithBindCapabilities(handle);
+
+        // THEN
+        try {
+            PhoneAccount account = builder.build();
+            assertEquals(invalidId, account.getAccountHandle().getId());
+            mRegistrar.registerPhoneAccount(account);
+            fail("failed to throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // pass test
+        } finally {
+            mRegistrar.unregisterPhoneAccount(handle);
+        }
+    }
+
+    /**
+     * Test that an {@link IllegalArgumentException} is thrown when a package registers a
+     * {@link PhoneAccount} with a { PhoneAccount#mLabel} that is over the character limit set
+     */
+    @Test
+    public void testInvalidLabelThrowsException() {
+        // GIVEN
+        String invalidLabel = INVALID_STR;
+        PhoneAccountHandle handle = makeQuickAccountHandle(TEST_ID);
+        PhoneAccount.Builder builder = new PhoneAccount.Builder(handle, invalidLabel)
+                .setCapabilities(PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS);
+
+        // WHEN
+        when(mAppLabelProxy.getAppLabel(anyString())).thenReturn(invalidLabel);
+
+        // THEN
+        try {
+            PhoneAccount account = builder.build();
+            assertEquals(invalidLabel, account.getLabel());
+            mRegistrar.registerPhoneAccount(account);
+            fail("failed to throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // pass test
+        } finally {
+            mRegistrar.unregisterPhoneAccount(handle);
+        }
+    }
+
+    /**
+     * Test that an {@link IllegalArgumentException} is thrown when a package registers a
+     * {@link PhoneAccount} with a {PhoneAccount#mShortDescription} that is over the character
+     * limit set
+     */
+    @Test
+    public void testInvalidShortDescriptionThrowsException() {
+        // GIVEN
+        String invalidShortDescription = INVALID_STR;
+        PhoneAccountHandle handle = makeQuickAccountHandle(TEST_ID);
+        PhoneAccount.Builder builder = makeBuilderWithBindCapabilities(handle)
+                .setShortDescription(invalidShortDescription);
+
+        // THEN
+        try {
+            PhoneAccount account = builder.build();
+            assertEquals(invalidShortDescription, account.getShortDescription());
+            mRegistrar.registerPhoneAccount(account);
+            fail("failed to throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // pass test
+        } finally {
+            mRegistrar.unregisterPhoneAccount(handle);
+        }
+    }
+
+    /**
+     * Test that an {@link IllegalArgumentException} is thrown when a package registers a
+     * {@link PhoneAccount} with a {PhoneAccount#mGroupId} that is over the character limit set
+     */
+    @Test
+    public void testInvalidGroupIdThrowsException() {
+        // GIVEN
+        String invalidGroupId = INVALID_STR;
+        PhoneAccountHandle handle = makeQuickAccountHandle(TEST_ID);
+        PhoneAccount.Builder builder = makeBuilderWithBindCapabilities(handle)
+                .setGroupId(invalidGroupId);
+
+        // THEN
+        try {
+            PhoneAccount account = builder.build();
+            assertEquals(invalidGroupId, account.getGroupId());
+            mRegistrar.registerPhoneAccount(account);
+            fail("failed to throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // pass test
+        } finally {
+            mRegistrar.unregisterPhoneAccount(handle);
+        }
+    }
+
+    /**
+     * Test that an {@link IllegalArgumentException} is thrown when a package registers a
+     * {@link PhoneAccount} with a {PhoneAccount#mExtras} that is over the character limit set
+     */
+    @Test
+    public void testInvalidExtraStringKeyThrowsException() {
+        // GIVEN
+        String invalidBundleKey = INVALID_STR;
+        String keyValue = "value";
+        Bundle extras = new Bundle();
+        extras.putString(invalidBundleKey, keyValue);
+        PhoneAccountHandle handle = makeQuickAccountHandle(TEST_ID);
+        PhoneAccount.Builder builder = makeBuilderWithBindCapabilities(handle)
+                .setExtras(extras);
+
+        // THEN
+        try {
+            PhoneAccount account = builder.build();
+            assertEquals(keyValue, account.getExtras().getString(invalidBundleKey));
+            mRegistrar.registerPhoneAccount(account);
+            fail("failed to throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // pass test
+        } finally {
+            mRegistrar.unregisterPhoneAccount(handle);
+        }
+    }
+
+    /**
+     * Test that an {@link IllegalArgumentException} is thrown when a package registers a
+     * {@link PhoneAccount} with a {PhoneAccount#mExtras} that is over the character limit set
+     */
+    @Test
+    public void testInvalidExtraStringValueThrowsException() {
+        // GIVEN
+        String extrasKey = "ExtrasStringKey";
+        String invalidBundleValue = INVALID_STR;
+        Bundle extras = new Bundle();
+        extras.putString(extrasKey, invalidBundleValue);
+        PhoneAccountHandle handle = makeQuickAccountHandle(TEST_ID);
+        PhoneAccount.Builder builder = makeBuilderWithBindCapabilities(handle)
+                .setExtras(extras);
+
+        // THEN
+        try {
+            PhoneAccount account = builder.build();
+            assertEquals(invalidBundleValue, account.getExtras().getString(extrasKey));
+            mRegistrar.registerPhoneAccount(account);
+            fail("failed to throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // pass test
+        } finally {
+            mRegistrar.unregisterPhoneAccount(handle);
+        }
+    }
+
+    /**
+     * Test that an {@link IllegalArgumentException} is thrown when a package registers a
+     * {@link PhoneAccount} with a {PhoneAccount#mExtras} that is over the (key,value) pair limit
+     */
+    @Test
+    public void testInvalidExtraElementsExceedsLimitAndThrowsException() {
+        // GIVEN
+        int invalidBundleExtrasLimit =
+                PhoneAccountRegistrar.MAX_PHONE_ACCOUNT_EXTRAS_KEY_PAIR_LIMIT + 1;
+        Bundle extras = new Bundle();
+        for (int i = 0; i < invalidBundleExtrasLimit; i++) {
+            extras.putString(UUID.randomUUID().toString(), "value");
+        }
+        PhoneAccountHandle handle = makeQuickAccountHandle(TEST_ID);
+        PhoneAccount.Builder builder = makeBuilderWithBindCapabilities(handle)
+                .setExtras(extras);
+        // THEN
+        try {
+            PhoneAccount account = builder.build();
+            assertEquals(invalidBundleExtrasLimit, account.getExtras().size());
+            mRegistrar.registerPhoneAccount(account);
+            fail("failed to throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // Test Pass
+        } finally {
+            mRegistrar.unregisterPhoneAccount(handle);
+        }
+    }
+
+    /**
+     * Ensure an IllegalArgumentException is thrown when adding more than 10 schemes for a single
+     * account
+     */
+    @Test
+    public void testLimitOnSchemeCount() {
+        PhoneAccountHandle handle = makeQuickAccountHandle(TEST_ID);
+        PhoneAccount.Builder builder = new PhoneAccount.Builder(handle, TEST_LABEL);
+        for (int i = 0; i < PhoneAccountRegistrar.MAX_PHONE_ACCOUNT_REGISTRATIONS + 1; i++) {
+            builder.addSupportedUriScheme(Integer.toString(i));
+        }
+        try {
+            mRegistrar.enforceLimitsOnSchemes(builder.build());
+            fail("should have hit exception in enforceLimitOnSchemes");
+        } catch (IllegalArgumentException e) {
+            // pass test
+        }
+    }
+
+    /**
+     * Ensure an IllegalArgumentException is thrown when adding more 256 chars for a single
+     * account
+     */
+    @Test
+    public void testLimitOnSchemeLength() {
+        PhoneAccountHandle handle = makeQuickAccountHandle(TEST_ID);
+        PhoneAccount.Builder builder = new PhoneAccount.Builder(handle, TEST_LABEL);
+        builder.addSupportedUriScheme(INVALID_STR);
+        try {
+            mRegistrar.enforceLimitsOnSchemes(builder.build());
+            fail("should have hit exception in enforceLimitOnSchemes");
+        } catch (IllegalArgumentException e) {
+            // pass test
+        }
+    }
+
+    /**
+     * Ensure an IllegalArgumentException is thrown when adding an address over the limit
+     */
+    @Test
+    public void testLimitOnAddress() {
+        String text = "a".repeat(100);
+        PhoneAccountHandle handle = makeQuickAccountHandle(TEST_ID);
+        PhoneAccount.Builder builder = makeBuilderWithBindCapabilities(handle)
+                .setAddress(Uri.fromParts(text, text, text));
+        try {
+            mRegistrar.registerPhoneAccount(builder.build());
+            fail("failed to throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // pass test
+        }
+        finally {
+            mRegistrar.unregisterPhoneAccount(handle);
+        }
+    }
+
+    /**
+     * Ensure an IllegalArgumentException is thrown when an Icon that throws an IOException is given
+     */
+    @Test
+    public void testLimitOnIcon() throws Exception {
+        Icon mockIcon = mock(Icon.class);
+        // GIVEN
+        PhoneAccount.Builder builder = makeBuilderWithBindCapabilities(
+                makeQuickAccountHandle(TEST_ID)).setIcon(mockIcon);
+        try {
+            // WHEN
+            Mockito.doThrow(new IOException())
+                    .when(mockIcon).writeToStream(any(OutputStream.class));
+            //THEN
+            mRegistrar.enforceIconSizeLimit(builder.build());
+            fail("failed to throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // pass test
+            assertTrue(e.getMessage().contains(PhoneAccountRegistrar.ICON_ERROR_MSG));
+        }
+    }
+
+    /**
+     * Ensure an IllegalArgumentException is thrown when providing a SubscriptionAddress that
+     * exceeds the PhoneAccountRegistrar limit.
+     */
+    @Test
+    public void testLimitOnSubscriptionAddress() throws Exception {
+        String text = "a".repeat(100);
+        PhoneAccount.Builder builder =  new PhoneAccount.Builder(makeQuickAccountHandle(TEST_ID),
+                TEST_LABEL).setSubscriptionAddress(Uri.fromParts(text, text, text));
+        try {
+            mRegistrar.enforceCharacterLimit(builder.build());
+            fail("failed to throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // pass test
+        }
+    }
+
+    /**
+     * PhoneAccounts with CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS do not require a
+     * ConnectionService. Ensure that such an account can be registered and fetched.
+     */
+    @Test
+    public void testFetchingTransactionalAccounts() {
+        PhoneAccount account = makeBuilderWithBindCapabilities(
+                makeQuickAccountHandle(TEST_ID)).build();
+
+        try {
+            assertEquals(0, mRegistrar.getAllPhoneAccounts(null, true).size());
+            registerAndEnableAccount(account);
+            assertEquals(1, mRegistrar.getAllPhoneAccounts(null, true).size());
+        } finally {
+            mRegistrar.unregisterPhoneAccount(account.getAccountHandle());
+        }
+    }
+
+    private static PhoneAccount.Builder makeBuilderWithBindCapabilities(PhoneAccountHandle handle) {
+        return new PhoneAccount.Builder(handle, TEST_LABEL)
+                .setCapabilities(PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS);
+    }
+
     private static ComponentName makeQuickConnectionServiceComponentName() {
         return new ComponentName(
                 "com.android.server.telecom.tests",
@@ -1268,9 +1719,17 @@
         return new PhoneAccountHandle(name, id, Process.myUserHandle());
     }
 
-    private PhoneAccount.Builder makeQuickAccountBuilder(String id, int idx) {
+    private static PhoneAccountHandle makeQuickAccountHandleForUser(
+            String id, UserHandle userHandle) {
+        return new PhoneAccountHandle(makeQuickConnectionServiceComponentName(), id, userHandle);
+    }
+
+    private PhoneAccount.Builder makeQuickAccountBuilder(
+            String id, int idx, UserHandle userHandle) {
         return new PhoneAccount.Builder(
-                makeQuickAccountHandle(id),
+                userHandle == null
+                        ? makeQuickAccountHandle(id)
+                        : makeQuickAccountHandleForUser(id, userHandle),
                 "label" + idx);
     }
 
@@ -1292,7 +1751,7 @@
     }
 
     private PhoneAccount makeQuickAccount(String id, int idx) {
-        return makeQuickAccountBuilder(id, idx)
+        return makeQuickAccountBuilder(id, idx, null)
                 .setAddress(Uri.parse("http://foo.com/" + idx))
                 .setSubscriptionAddress(Uri.parse("tel:555-000" + idx))
                 .setCapabilities(idx)
@@ -1309,7 +1768,7 @@
      */
     private PhoneAccount makeQuickSimAccount(int simId) {
         PhoneAccount simAccount =
-                makeQuickAccountBuilder("sim" + simId, simId)
+                makeQuickAccountBuilder("sim" + simId, simId, null)
                         .setCapabilities(
                                 PhoneAccount.CAPABILITY_CALL_PROVIDER
                                         | PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
diff --git a/tests/src/com/android/server/telecom/tests/RingerTest.java b/tests/src/com/android/server/telecom/tests/RingerTest.java
index 12420a8..cf5b791 100644
--- a/tests/src/com/android/server/telecom/tests/RingerTest.java
+++ b/tests/src/com/android/server/telecom/tests/RingerTest.java
@@ -27,14 +27,17 @@
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 import android.app.NotificationManager;
+import android.content.ComponentName;
 import android.content.Context;
 import android.media.AudioAttributes;
 import android.media.AudioManager;
@@ -42,10 +45,12 @@
 import android.media.VolumeShaper;
 import android.net.Uri;
 import android.os.Bundle;
-import android.os.Parcel;
+import android.os.UserHandle;
+import android.os.UserManager;
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
+import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -66,69 +71,39 @@
 import org.mockito.Mock;
 import org.mockito.Spy;
 
-import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
 
 @RunWith(JUnit4.class)
 public class RingerTest extends TelecomTestCase {
     private static final Uri FAKE_RINGTONE_URI = Uri.parse("content://media/fake/audio/1729");
-    private static class UriVibrationEffect extends VibrationEffect {
-        final Uri mUri;
-
-        private UriVibrationEffect(Uri uri) {
-            mUri = uri;
-        }
-
-        @Override
-        public VibrationEffect resolve(int defaultAmplitude) {
-            return this;
-        }
-
-        @Override
-        public VibrationEffect scale(float scaleFactor) {
-            return this;
-        }
-
-        @Override
-        public void validate() {
-            // not needed
-        }
-
-        @Override
-        public long getDuration() {
-            return 0;
-        }
-
-        @Override
-        public void writeToParcel(Parcel dest, int flags) {
-            // not needed
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            UriVibrationEffect that = (UriVibrationEffect) o;
-            return Objects.equals(mUri, that.mUri);
-        }
-    }
+    // Returned when the a URI-based VibrationEffect is attempted, to avoid depending on actual
+    // device configuration for ringtone URIs. The actual Uri can be verified via the
+    // VibrationEffectProxy mock invocation.
+    private static final VibrationEffect URI_VIBRATION_EFFECT =
+            VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
 
     @Mock InCallTonePlayer.Factory mockPlayerFactory;
     @Mock SystemSettingsUtil mockSystemSettingsUtil;
-    @Mock AsyncRingtonePlayer mockRingtonePlayer;
     @Mock RingtoneFactory mockRingtoneFactory;
     @Mock Vibrator mockVibrator;
     @Mock InCallController mockInCallController;
     @Mock NotificationManager mockNotificationManager;
+    @Mock Ringer.AccessibilityManagerAdapter mockAccessibilityManagerAdapter;
+
     @Spy Ringer.VibrationEffectProxy spyVibrationEffectProxy;
 
     @Mock InCallTonePlayer mockTonePlayer;
     @Mock Call mockCall1;
     @Mock Call mockCall2;
 
+    private static final PhoneAccountHandle PA_HANDLE =
+            new PhoneAccountHandle(new ComponentName("pa_pkg", "pa_cls"),
+                    "pa_id");
+
+    boolean mIsHapticPlaybackSupported = true;  // Note: initializeRinger() after changes.
+    AsyncRingtonePlayer asyncRingtonePlayer = new AsyncRingtonePlayer();
     Ringer mRingerUnderTest;
     AudioManager mockAudioManager;
-    CompletableFuture<Boolean> mFuture = new CompletableFuture<>();
     CompletableFuture<Void> mRingCompletionFuture = new CompletableFuture<>();
 
     @Override
@@ -136,28 +111,33 @@
     public void setUp() throws Exception {
         super.setUp();
         mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
-        doAnswer(invocation -> {
-            Uri ringtoneUriForEffect = invocation.getArgument(0);
-            return new UriVibrationEffect(ringtoneUriForEffect);
-        }).when(spyVibrationEffectProxy).get(any(), any());
+        doReturn(URI_VIBRATION_EFFECT).when(spyVibrationEffectProxy).get(any(), any());
         when(mockPlayerFactory.createPlayer(anyInt())).thenReturn(mockTonePlayer);
-        mockAudioManager =
-                (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        mockAudioManager = mContext.getSystemService(AudioManager.class);
         when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
-        when(mockSystemSettingsUtil.isHapticPlaybackSupported(any(Context.class))).thenReturn(true);
-        mockNotificationManager =
-                (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+        when(mockSystemSettingsUtil.isHapticPlaybackSupported(any(Context.class)))
+                .thenAnswer((invocation) -> mIsHapticPlaybackSupported);
+        mockNotificationManager =mContext.getSystemService(NotificationManager.class);
         when(mockTonePlayer.startTone()).thenReturn(true);
         when(mockNotificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(true);
         when(mockRingtoneFactory.hasHapticChannels(any(Ringtone.class))).thenReturn(false);
-        when(mockRingtonePlayer.play(any(RingtoneFactory.class), any(Call.class),
-                nullable(VolumeShaper.Configuration.class), anyBoolean(), anyBoolean()))
-                .thenReturn(mFuture);
-        mRingerUnderTest = new Ringer(mockPlayerFactory, mContext, mockSystemSettingsUtil,
-                mockRingtonePlayer, mockRingtoneFactory, mockVibrator, spyVibrationEffectProxy,
-                mockInCallController);
         when(mockCall1.getState()).thenReturn(CallState.RINGING);
         when(mockCall2.getState()).thenReturn(CallState.RINGING);
+        when(mockCall1.getUserHandleFromTargetPhoneAccount()).thenReturn(PA_HANDLE.getUserHandle());
+        when(mockCall2.getUserHandleFromTargetPhoneAccount()).thenReturn(PA_HANDLE.getUserHandle());
+
+        createRingerUnderTest();
+    }
+
+    /**
+     * (Re-)Creates the Ringer for the test. This needs to be called if changing final properties,
+     * like mIsHapticPlaybackSupported.
+     */
+    private void createRingerUnderTest() {
+        mRingerUnderTest = new Ringer(mockPlayerFactory, mContext, mockSystemSettingsUtil,
+                asyncRingtonePlayer, mockRingtoneFactory, mockVibrator, spyVibrationEffectProxy,
+                mockInCallController, mockNotificationManager, mockAccessibilityManagerAdapter);
+        // This future is used to wait for AsyncRingtonePlayer to finish its part.
         mRingerUnderTest.setBlockOnRingingFuture(mRingCompletionFuture);
     }
 
@@ -169,33 +149,30 @@
 
     @SmallTest
     @Test
-    public void testNoActionInTheaterMode() {
+    public void testNoActionInTheaterMode() throws Exception {
         // Start call waiting to make sure that it doesn't stop when we start ringing
-        mFuture.complete(false); // not using audio coupled haptics
         mRingerUnderTest.startCallWaiting(mockCall1);
         when(mockSystemSettingsUtil.isTheaterModeOn(any(Context.class))).thenReturn(true);
-        assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
+        assertFalse(startRingingAndWaitForAsync(mockCall2, false));
+        verifyZeroInteractions(mockRingtoneFactory);
         verify(mockTonePlayer, never()).stopTone();
-        verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class),
-                nullable(VolumeShaper.Configuration.class), anyBoolean(), anyBoolean());
         verify(mockVibrator, never())
                 .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
     }
 
     @SmallTest
     @Test
-    public void testNoActionWithExternalRinger() {
-        mFuture.complete(false); // not using audio coupled haptics
+    public void testNoActionWithExternalRinger() throws Exception {
         Bundle externalRingerExtra = new Bundle();
         externalRingerExtra.putBoolean(TelecomManager.EXTRA_CALL_HAS_IN_BAND_RINGTONE, true);
         when(mockCall1.getIntentExtras()).thenReturn(externalRingerExtra);
         when(mockCall2.getIntentExtras()).thenReturn(externalRingerExtra);
         // Start call waiting to make sure that it doesn't stop when we start ringing
         mRingerUnderTest.startCallWaiting(mockCall1);
-        assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
+        assertFalse(startRingingAndWaitForAsync(mockCall2, false));
+
+        verifyZeroInteractions(mockRingtoneFactory);
         verify(mockTonePlayer, never()).stopTone();
-        verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class),
-                nullable(VolumeShaper.Configuration.class), anyBoolean(), anyBoolean());
         verify(mockVibrator, never())
                 .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
     }
@@ -203,15 +180,15 @@
     @SmallTest
     @Test
     public void testNoActionWhenDialerRings() throws Exception {
-        mFuture.complete(false); // not using audio coupled haptics
         // Start call waiting to make sure that it doesn't stop when we start ringing
         mRingerUnderTest.startCallWaiting(mockCall1);
-        when(mockInCallController.doesConnectedDialerSupportRinging()).thenReturn(true);
-        assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
-        mRingCompletionFuture.get();
+        when(mockInCallController.doesConnectedDialerSupportRinging(
+                any(UserHandle.class))).thenReturn(true);
+        ensureRingerIsNotAudible();
+        assertFalse(startRingingAndWaitForAsync(mockCall2, false));
+
+        verifyZeroInteractions(mockRingtoneFactory);
         verify(mockTonePlayer, never()).stopTone();
-        verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class),
-                nullable(VolumeShaper.Configuration.class), anyBoolean(), anyBoolean());
         verify(mockVibrator, never())
                 .vibrate(any(VibrationEffect.class), any(AudioAttributes.class));
     }
@@ -219,49 +196,46 @@
     @SmallTest
     @Test
     public void testAudioFocusStillAcquiredWhenDialerRings() throws Exception {
-        mFuture.complete(false); // not using audio coupled haptics
+
         // Start call waiting to make sure that it doesn't stop when we start ringing
         mRingerUnderTest.startCallWaiting(mockCall1);
-        when(mockInCallController.doesConnectedDialerSupportRinging()).thenReturn(true);
+        when(mockInCallController.doesConnectedDialerSupportRinging(
+                any(UserHandle.class))).thenReturn(true);
         ensureRingerIsAudible();
-        assertTrue(mRingerUnderTest.startRinging(mockCall2, false));
-        mRingCompletionFuture.get();
+        assertTrue(startRingingAndWaitForAsync(mockCall2, false));
+        verifyZeroInteractions(mockRingtoneFactory);
         verify(mockTonePlayer, never()).stopTone();
-        verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class),
-                nullable(VolumeShaper.Configuration.class), anyBoolean(), anyBoolean());
         verify(mockVibrator, never())
                 .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
     }
 
     @SmallTest
     @Test
-    public void testNoActionWhenCallIsSelfManaged() {
-        mFuture.complete(false); // not using audio coupled haptics
+    public void testNoActionWhenCallIsSelfManaged() throws Exception {
         // Start call waiting to make sure that it doesn't stop when we start ringing
         mRingerUnderTest.startCallWaiting(mockCall1);
         when(mockCall2.isSelfManaged()).thenReturn(true);
         // We do want to acquire audio focus when self-managed
-        assertTrue(mRingerUnderTest.startRinging(mockCall2, true));
+        assertTrue(startRingingAndWaitForAsync(mockCall2, true));
+
+        verifyZeroInteractions(mockRingtoneFactory);
         verify(mockTonePlayer, never()).stopTone();
-        verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class),
-                nullable(VolumeShaper.Configuration.class), anyBoolean(), anyBoolean());
         verify(mockVibrator, never())
                 .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
     }
 
     @SmallTest
     @Test
-    public void testCallWaitingButNoRingForSpecificContacts() {
-        mFuture.complete(false); // not using audio coupled haptics
+    public void testCallWaitingButNoRingForSpecificContacts() throws Exception {
         when(mockNotificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(false);
         // Start call waiting to make sure that it does stop when we start ringing
         mRingerUnderTest.startCallWaiting(mockCall1);
         verify(mockTonePlayer).startTone();
 
-        assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
+        assertFalse(startRingingAndWaitForAsync(mockCall2, false));
+
+        verifyZeroInteractions(mockRingtoneFactory);
         verify(mockTonePlayer).stopTone();
-        verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class),
-                nullable(VolumeShaper.Configuration.class), anyBoolean(), anyBoolean());
         verify(mockVibrator, never())
                 .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
     }
@@ -269,16 +243,19 @@
     @SmallTest
     @Test
     public void testNoVibrateDueToAudioCoupledHaptics() throws Exception {
+        Ringtone mockRingtone = ensureRingtoneMocked();
+
         mRingerUnderTest.startCallWaiting(mockCall1);
         ensureRingerIsAudible();
         enableVibrationWhenRinging();
         // Pretend we're using audio coupled haptics.
-        mFuture.complete(true);
-        assertTrue(mRingerUnderTest.startRinging(mockCall2, false));
-        mRingCompletionFuture.get();
+        setIsUsingHaptics(mockRingtone, true);
+        assertTrue(startRingingAndWaitForAsync(mockCall1, false));
+        verify(mockRingtoneFactory, times(1))
+            .getRingtone(any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean());
+        verifyNoMoreInteractions(mockRingtoneFactory);
         verify(mockTonePlayer).stopTone();
-        verify(mockRingtonePlayer).play(any(RingtoneFactory.class), any(Call.class), isNull(),
-                eq(true) /* isRingerAudible */, eq(true) /* isVibrationEnabled */);
+        verify(mockRingtone).play();
         verify(mockVibrator, never()).vibrate(any(VibrationEffect.class),
                 any(VibrationAttributes.class));
     }
@@ -286,17 +263,24 @@
     @SmallTest
     @Test
     public void testVibrateButNoRingForNullRingtone() throws Exception {
+        when(mockRingtoneFactory.getRingtone(
+                 any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean()))
+            .thenReturn(null);
+
         mRingerUnderTest.startCallWaiting(mockCall1);
-        when(mockRingtoneFactory.getRingtone(any(Call.class))).thenReturn(null);
         when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
-        mFuture.complete(false); // not using audio coupled haptics
         enableVibrationWhenRinging();
-        assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
-        mRingCompletionFuture.get();
+        // The ringtone isn't known to be null until the async portion after the call completes,
+        // so startRinging still returns true here as there should nominally be a ringtone.
+        // Notably, vibration still happens in this scenario.
+        assertTrue(startRingingAndWaitForAsync(mockCall2, false));
         verify(mockTonePlayer).stopTone();
-        // Try to play a silent haptics ringtone
-        verify(mockRingtonePlayer).play(any(RingtoneFactory.class), any(Call.class), isNull(),
-                eq(false) /* isRingerAudible */, eq(true) /* isVibrationEnabled */);
+
+        // Just the one call to mockRingtoneFactory, which returned null.
+        verify(mockRingtoneFactory).getRingtone(
+                any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean());
+        verifyNoMoreInteractions(mockRingtoneFactory);
+
         // Play default vibration when future completes with no audio coupled haptics
         verify(mockVibrator).vibrate(eq(mRingerUnderTest.mDefaultVibrationEffect),
                 any(VibrationAttributes.class));
@@ -305,19 +289,39 @@
     @SmallTest
     @Test
     public void testVibrateButNoRingForSilentRingtone() throws Exception {
+        Ringtone mockRingtone = ensureRingtoneMocked();
+
         mRingerUnderTest.startCallWaiting(mockCall1);
-        Ringtone mockRingtone = mock(Ringtone.class);
-        when(mockRingtoneFactory.getRingtone(any(Call.class))).thenReturn(mockRingtone);
+        when(mockRingtoneFactory.getRingtone(any(Call.class), eq(null), anyBoolean()))
+            .thenReturn(mockRingtone);
         when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
         when(mockAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(0);
-        mFuture.complete(false); // not using audio coupled haptics
         enableVibrationWhenRinging();
-        assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
-        mRingCompletionFuture.get();
+        assertFalse(startRingingAndWaitForAsync(mockCall2, false));
         verify(mockTonePlayer).stopTone();
         // Try to play a silent haptics ringtone
-        verify(mockRingtonePlayer).play(any(RingtoneFactory.class), any(Call.class), isNull(),
-                eq(false) /* isRingerAudible */, eq(true) /* isVibrationEnabled */);
+        verify(mockRingtoneFactory, times(1)).getHapticOnlyRingtone();
+        verifyNoMoreInteractions(mockRingtoneFactory);
+        verify(mockRingtone).play();
+
+        // Play default vibration when future completes with no audio coupled haptics
+        verify(mockVibrator).vibrate(eq(mRingerUnderTest.mDefaultVibrationEffect),
+                any(VibrationAttributes.class));
+    }
+
+    @SmallTest
+    @Test
+    public void testVibrateButNoRingForSilentRingtoneWithoutAudioHapticSupport() throws Exception {
+        mIsHapticPlaybackSupported = false;
+        createRingerUnderTest();  // Needed after changing haptic playback support.
+        mRingerUnderTest.startCallWaiting(mockCall1);
+        when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
+        when(mockAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(0);
+        enableVibrationWhenRinging();
+        assertFalse(startRingingAndWaitForAsync(mockCall2, false));
+        verify(mockTonePlayer).stopTone();
+        verifyZeroInteractions(mockRingtoneFactory);
+
         // Play default vibration when future completes with no audio coupled haptics
         verify(mockVibrator).vibrate(eq(mRingerUnderTest.mDefaultVibrationEffect),
                 any(VibrationAttributes.class));
@@ -326,19 +330,20 @@
     @SmallTest
     @Test
     public void testAudioCoupledHapticsForSilentRingtone() throws Exception {
+        Ringtone mockRingtone = ensureRingtoneMocked();
+
         mRingerUnderTest.startCallWaiting(mockCall1);
-        Ringtone mockRingtone = mock(Ringtone.class);
-        when(mockRingtoneFactory.getRingtone(any(Call.class))).thenReturn(mockRingtone);
         when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
         when(mockAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(0);
-        mFuture.complete(true); // using audio coupled haptics
+        setIsUsingHaptics(mockRingtone, true);
         enableVibrationWhenRinging();
-        assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
-        mRingCompletionFuture.get();
+        assertFalse(startRingingAndWaitForAsync(mockCall2, false));
+
+        verify(mockRingtoneFactory, times(1)).getHapticOnlyRingtone();
+        verifyNoMoreInteractions(mockRingtoneFactory);
         verify(mockTonePlayer).stopTone();
         // Try to play a silent haptics ringtone
-        verify(mockRingtonePlayer).play(any(RingtoneFactory.class), any(Call.class), isNull(),
-                eq(false) /* isRingerAudible */, eq(true) /* isVibrationEnabled */);
+        verify(mockRingtone).play();
         // Skip vibration for audio coupled haptics
         verify(mockVibrator, never()).vibrate(any(VibrationEffect.class),
                 any(VibrationAttributes.class));
@@ -346,60 +351,36 @@
 
     @SmallTest
     @Test
-    public void testStopRingingBeforeHapticsLookupComplete() throws Exception {
-        enableVibrationWhenRinging();
-        Ringtone mockRingtone = mock(Ringtone.class);
-        when(mockRingtoneFactory.getRingtone(nullable(Call.class))).thenReturn(mockRingtone);
-        when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
-
-        mRingerUnderTest.startRinging(mockCall1, false);
-        // Make sure we haven't started the vibrator yet, but have started ringing.
-        verify(mockRingtonePlayer).play(nullable(RingtoneFactory.class), nullable(Call.class),
-                nullable(VolumeShaper.Configuration.class), anyBoolean(), anyBoolean());
-        verify(mockVibrator, never()).vibrate(nullable(VibrationEffect.class),
-                nullable(VibrationAttributes.class));
-        // Simulate something stopping the ringer
-        mRingerUnderTest.stopRinging();
-        verify(mockRingtonePlayer).stop();
-        verify(mockVibrator, never()).cancel();
-        // Simulate the haptics computation finishing
-        mFuture.complete(false);
-        // Then make sure that we don't actually start vibrating.
-        verify(mockVibrator, never()).vibrate(nullable(VibrationEffect.class),
-                nullable(VibrationAttributes.class));
-    }
-
-    @SmallTest
-    @Test
     public void testCustomVibrationForRingtone() throws Exception {
         mRingerUnderTest.startCallWaiting(mockCall1);
-        Ringtone mockRingtone = mock(Ringtone.class);
+        Ringtone mockRingtone = ensureRingtoneMocked();
         when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
-        when(mockRingtoneFactory.getRingtone(any(Call.class))).thenReturn(mockRingtone);
         when(mockRingtone.getUri()).thenReturn(FAKE_RINGTONE_URI);
-        mFuture.complete(false); // not using audio coupled haptics
         enableVibrationWhenRinging();
-        assertTrue(mRingerUnderTest.startRinging(mockCall2, false));
-        mRingCompletionFuture.get();
+        assertTrue(startRingingAndWaitForAsync(mockCall2, false));
         verify(mockTonePlayer).stopTone();
-        verify(mockRingtonePlayer).play(any(RingtoneFactory.class), any(Call.class), eq(null),
-                eq(true) /* isRingerAudible */, eq(true) /* isVibrationEnabled */);
-        verify(mockVibrator).vibrate(eq(spyVibrationEffectProxy.get(FAKE_RINGTONE_URI, mContext)),
-                any(VibrationAttributes.class));
+        verify(mockRingtoneFactory, times(1))
+            .getRingtone(any(Call.class), isNull(), anyBoolean());
+        verifyNoMoreInteractions(mockRingtoneFactory);
+        verify(mockRingtone).play();
+        verify(spyVibrationEffectProxy).get(eq(FAKE_RINGTONE_URI), any(Context.class));
+        verify(mockVibrator).vibrate(eq(URI_VIBRATION_EFFECT), any(VibrationAttributes.class));
     }
 
     @SmallTest
     @Test
     public void testRingAndNoVibrate() throws Exception {
+        Ringtone mockRingtone = ensureRingtoneMocked();
+
         mRingerUnderTest.startCallWaiting(mockCall1);
         ensureRingerIsAudible();
-        mFuture.complete(false); // not using audio coupled haptics
         enableVibrationOnlyWhenNotRinging();
-        assertTrue(mRingerUnderTest.startRinging(mockCall2, false));
-        mRingCompletionFuture.get();
+        assertTrue(startRingingAndWaitForAsync(mockCall2, false));
+        verify(mockRingtoneFactory, times(1))
+            .getRingtone(any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean());
+        verifyNoMoreInteractions(mockRingtoneFactory);
         verify(mockTonePlayer).stopTone();
-        verify(mockRingtonePlayer).play(any(RingtoneFactory.class), any(Call.class), eq(null),
-                eq(true) /* isRingerAudible */, eq(false) /* isVibrationEnabled */);
+        verify(mockRingtone).play();
         verify(mockVibrator, never())
                 .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
     }
@@ -407,52 +388,31 @@
     @SmallTest
     @Test
     public void testRingWithRampingRinger() throws Exception {
+        Ringtone mockRingtone = ensureRingtoneMocked();
+
         mRingerUnderTest.startCallWaiting(mockCall1);
         ensureRingerIsAudible();
         enableRampingRinger();
-        mFuture.complete(false); // not using audio coupled haptics
         enableVibrationWhenRinging();
-        assertTrue(mRingerUnderTest.startRinging(mockCall2, false));
-        mRingCompletionFuture.get();
+        assertTrue(startRingingAndWaitForAsync(mockCall2, false));
+        verify(mockRingtoneFactory, times(1))
+            .getRingtone(any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean());
+        verifyNoMoreInteractions(mockRingtoneFactory);
         verify(mockTonePlayer).stopTone();
-        verify(mockRingtonePlayer).play(
-            any(RingtoneFactory.class), any(Call.class), any(VolumeShaper.Configuration.class),
-                eq(true) /* isRingerAudible */, eq(true) /* isVibrationEnabled */);
+        verify(mockRingtone).play();
     }
 
     @SmallTest
     @Test
-    public void testSilentRingWithHfpStillAcquiresFocus1() throws Exception {
+    public void testSilentRingWithHfpStillAcquiresFocus() throws Exception {
         mRingerUnderTest.startCallWaiting(mockCall1);
-        Ringtone mockRingtone = mock(Ringtone.class);
-        when(mockRingtoneFactory.getRingtone(any(Call.class))).thenReturn(mockRingtone);
         when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
         when(mockAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(0);
-        mFuture.complete(false); // not using audio coupled haptics
         enableVibrationOnlyWhenNotRinging();
-        assertTrue(mRingerUnderTest.startRinging(mockCall2, true));
-        mRingCompletionFuture.get();
+        assertTrue(startRingingAndWaitForAsync(mockCall2, true));
         verify(mockTonePlayer).stopTone();
-        verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class),
-                nullable(VolumeShaper.Configuration.class), anyBoolean(), anyBoolean());
-        verify(mockVibrator, never())
-                .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
-    }
-
-    @SmallTest
-    @Test
-    public void testSilentRingWithHfpStillAcquiresFocus2() throws Exception {
-        mRingerUnderTest.startCallWaiting(mockCall1);
-        when(mockRingtoneFactory.getRingtone(any(Call.class))).thenReturn(null);
-        when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
-        when(mockAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(0);
-        mFuture.complete(false); // not using audio coupled haptics
-        enableVibrationOnlyWhenNotRinging();
-        assertTrue(mRingerUnderTest.startRinging(mockCall2, true));
-        mRingCompletionFuture.get();
-        verify(mockTonePlayer).stopTone();
-        verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class),
-                nullable(VolumeShaper.Configuration.class), anyBoolean(), anyBoolean());
+        // Ringer not audible, so never tries to create a ringtone.
+        verifyZeroInteractions(mockRingtoneFactory);
         verify(mockVibrator, never())
                 .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
     }
@@ -461,27 +421,142 @@
     @Test
     public void testRingAndVibrateForAllowedCallInDndMode() throws Exception {
         mRingerUnderTest.startCallWaiting(mockCall1);
-        Ringtone mockRingtone = mock(Ringtone.class);
-        when(mockRingtoneFactory.getRingtone(any(Call.class))).thenReturn(mockRingtone);
+        Ringtone mockRingtone = ensureRingtoneMocked();
         when(mockNotificationManager.getZenMode()).thenReturn(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
         when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_SILENT);
         when(mockAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(100);
-        mFuture.complete(true); // using audio coupled haptics
         enableVibrationWhenRinging();
-        assertTrue(mRingerUnderTest.startRinging(mockCall2, true));
-        mRingCompletionFuture.get();
+        assertTrue(startRingingAndWaitForAsync(mockCall2, true));
+        verify(mockRingtoneFactory, times(1))
+            .getRingtone(any(Call.class), isNull(), anyBoolean());
+        verifyNoMoreInteractions(mockRingtoneFactory);
         verify(mockTonePlayer).stopTone();
-        verify(mockRingtonePlayer).play(any(RingtoneFactory.class), any(Call.class), eq(null),
-                eq(true) /* isRingerAudible */, eq(true) /* isVibrationEnabled */);
+        verify(mockRingtone).play();
+    }
+
+    /**
+     * assert {@link Ringer#shouldRingForContact(Call, Context) } sets the Call object with suppress
+     * caller
+     *
+     * @throws Exception; should not throw exception.
+     */
+    @Test
+    public void testShouldRingForContact_CallSuppressed() throws Exception {
+        // WHEN
+        when(mockCall1.wasDndCheckComputedForCall()).thenReturn(false);
+        when(mockCall1.getHandle()).thenReturn(Uri.parse(""));
+
+        when(mContext.getSystemService(NotificationManager.class)).thenReturn(
+                mockNotificationManager);
+        when(mockNotificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(false);
+
+        // THEN
+        assertFalse(mRingerUnderTest.shouldRingForContact(mockCall1));
+        verify(mockCall1, atLeastOnce()).setCallIsSuppressedByDoNotDisturb(true);
+    }
+
+    /**
+     * assert {@link Ringer#shouldRingForContact(Call, Context) } sets the Call object with ring
+     * caller
+     *
+     * @throws Exception; should not throw exception.
+     */
+    @Test
+    public void testShouldRingForContact_CallShouldRing() throws Exception {
+        // WHEN
+        when(mockCall1.wasDndCheckComputedForCall()).thenReturn(false);
+        when(mockCall1.getHandle()).thenReturn(Uri.parse(""));
+        when(mockNotificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(true);
+
+        // THEN
+        assertTrue(mRingerUnderTest.shouldRingForContact(mockCall1));
+        verify(mockCall1, atLeastOnce()).setCallIsSuppressedByDoNotDisturb(false);
+    }
+
+    @Test
+    public void testNoFlashNotificationWhenCallSuppressed() throws Exception {
+        ensureRingtoneMocked();
+        // Start call waiting to make sure that it doesn't stop when we start ringing
+        mRingerUnderTest.startCallWaiting(mockCall1);
+        when(mockCall2.wasDndCheckComputedForCall()).thenReturn(false);
+        when(mockCall2.getHandle()).thenReturn(Uri.parse(""));
+        when(mockNotificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(false);
+
+        assertFalse(mRingerUnderTest.shouldRingForContact(mockCall2));
+        assertFalse(startRingingAndWaitForAsync(mockCall2, false));
+        verify(mockAccessibilityManagerAdapter, never())
+                .startFlashNotificationSequence(any(Context.class), anyInt());
+    }
+
+    @Test
+    public void testStartFlashNotificationWhenRingStarts() throws Exception {
+        ensureRingtoneMocked();
+        // Start call waiting to make sure that it doesn't stop when we start ringing
+        mRingerUnderTest.startCallWaiting(mockCall1);
+        when(mockCall2.wasDndCheckComputedForCall()).thenReturn(false);
+        when(mockCall2.getHandle()).thenReturn(Uri.parse(""));
+        when(mockNotificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(true);
+
+        assertTrue(mRingerUnderTest.shouldRingForContact(mockCall2));
+        assertTrue(startRingingAndWaitForAsync(mockCall2, false));
+        verify(mockAccessibilityManagerAdapter, atLeastOnce())
+                .startFlashNotificationSequence(any(Context.class), anyInt());
+    }
+
+    @Test
+    public void testStopFlashNotificationWhenRingStops() throws Exception {
+        ensureRingtoneMocked();
+        // Start call waiting to make sure that it doesn't stop when we start ringing
+        mRingerUnderTest.startCallWaiting(mockCall1);
+        when(mockCall2.wasDndCheckComputedForCall()).thenReturn(false);
+        when(mockCall2.getHandle()).thenReturn(Uri.parse(""));
+        when(mockNotificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(true);
+
+        assertTrue(mRingerUnderTest.shouldRingForContact(mockCall2));
+        assertTrue(mRingerUnderTest.startRinging(mockCall2, false));
+        mRingerUnderTest.stopRinging();
+        verify(mockAccessibilityManagerAdapter, atLeastOnce())
+                .stopFlashNotificationSequence(any(Context.class));
+        mRingCompletionFuture.get();  // Don't leak async work.
+    }
+
+    @SmallTest
+    @Test
+    public void testNoRingingForQuietProfile() throws Exception {
+        UserManager um = mContext.getSystemService(UserManager.class);
+        when(um.isManagedProfile(PA_HANDLE.getUserHandle().getIdentifier())).thenReturn(true);
+        when(um.isQuietModeEnabled(PA_HANDLE.getUserHandle())).thenReturn(true);
+        // We don't want to acquire audio focus when self-managed
+        assertFalse(startRingingAndWaitForAsync(mockCall2, true));
+
+        verify(mockTonePlayer, never()).stopTone();
+        verifyZeroInteractions(mockRingtoneFactory);
+        verify(mockVibrator, never())
+                .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
+    }
+
+    /**
+     * Call startRinging and wait for its effects to have played out, to allow reliable assertions
+     * after it. The effects are generally "start playing ringtone" and "start vibration" - not
+     * waiting for anything open-ended.
+     */
+    private boolean startRingingAndWaitForAsync(Call mockCall2, boolean isHfpDeviceAttached)
+            throws Exception {
+        boolean result = mRingerUnderTest.startRinging(mockCall2, isHfpDeviceAttached);
+        mRingCompletionFuture.get();
+        return result;
     }
 
     private void ensureRingerIsAudible() {
-        Ringtone mockRingtone = mock(Ringtone.class);
-        when(mockRingtoneFactory.getRingtone(any(Call.class))).thenReturn(mockRingtone);
         when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
         when(mockAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(100);
     }
 
+    private void ensureRingerIsNotAudible() {
+        when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
+        when(mockAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(0);
+    }
+
     private void enableVibrationWhenRinging() {
         when(mockVibrator.hasVibrator()).thenReturn(true);
         when(mockSystemSettingsUtil.isRingVibrationEnabled(any(Context.class))).thenReturn(true);
@@ -495,4 +570,21 @@
     private void enableRampingRinger() {
         when(mockSystemSettingsUtil.isRampingRingerEnabled(any(Context.class))).thenReturn(true);
     }
+
+    private void setIsUsingHaptics(Ringtone mockRingtone, boolean useHaptics) {
+        // Note: using haptics can also depend on mIsHapticPlaybackSupported. If changing
+        // that, the ringerUnderTest needs to be re-created.
+        when(mockSystemSettingsUtil.isAudioCoupledVibrationForRampingRingerEnabled())
+            .thenReturn(useHaptics);
+        when(mockRingtone.hasHapticChannels()).thenReturn(useHaptics);
+    }
+
+    private Ringtone ensureRingtoneMocked() {
+        Ringtone mockRingtone = mock(Ringtone.class);
+        when(mockRingtoneFactory.getRingtone(
+                any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean()))
+                .thenReturn(mockRingtone);
+        when(mockRingtoneFactory.getHapticOnlyRingtone()).thenReturn(mockRingtone);
+        return mockRingtone;
+    }
 }
diff --git a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
index f6f2ae2..7b5afe6 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
@@ -18,11 +18,13 @@
 
 import static android.Manifest.permission.CALL_PHONE;
 import static android.Manifest.permission.CALL_PRIVILEGED;
+import static android.Manifest.permission.MANAGE_OWN_CALLS;
 import static android.Manifest.permission.MODIFY_PHONE_STATE;
 import static android.Manifest.permission.READ_PHONE_NUMBERS;
 import static android.Manifest.permission.READ_PHONE_STATE;
 import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
 
+import android.Manifest;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.content.ComponentName;
@@ -31,13 +33,16 @@
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.OutcomeReceiver;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.telecom.CallAttributes;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
@@ -45,7 +50,9 @@
 import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.internal.telecom.ICallEventCallback;
 import com.android.internal.telecom.ITelecomService;
+import com.android.server.telecom.AnomalyReporterAdapter;
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallIntentProcessor;
 import com.android.server.telecom.CallState;
@@ -56,6 +63,9 @@
 import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.components.UserCallIntentProcessor;
 import com.android.server.telecom.components.UserCallIntentProcessorFactory;
+import com.android.server.telecom.voip.IncomingCallTransaction;
+import com.android.server.telecom.voip.OutgoingCallTransaction;
+import com.android.server.telecom.voip.TransactionManager;
 
 import org.junit.After;
 import org.junit.Before;
@@ -66,7 +76,6 @@
 import org.mockito.ArgumentMatcher;
 import org.mockito.Mock;
 
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.Executor;
@@ -78,6 +87,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.nullable;
@@ -94,13 +104,18 @@
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.isA;
 import static org.mockito.Mockito.when;
 
 @RunWith(JUnit4.class)
 public class TelecomServiceImplTest extends TelecomTestCase {
 
+    private static final String CALLING_PACKAGE = TelecomServiceImplTest.class.getPackageName();
+    private static final String TEST_NAME = "Alan Turing";
+    private static final Uri TEST_URI = Uri.fromParts("tel", "abc", "123");
     public static final String TEST_PACKAGE = "com.test";
     public static final String PACKAGE_NAME = "test";
 
@@ -174,6 +189,9 @@
     @Mock private UserCallIntentProcessor mUserCallIntentProcessor;
     private PackageManager mPackageManager;
     @Mock private ApplicationInfo mApplicationInfo;
+    @Mock private ICallEventCallback mICallEventCallback;
+    @Mock private TransactionManager mTransactionManager;
+    @Mock private AnomalyReporterAdapter mAnomalyReporterAdapter;
 
     private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
 
@@ -203,6 +221,8 @@
         doReturn(mContext).when(mContext).createContextAsUser(any(UserHandle.class), anyInt());
         doNothing().when(mContext).sendBroadcastAsUser(any(Intent.class), any(UserHandle.class),
                 anyString());
+        when(mContext.checkCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
         doAnswer(invocation -> {
             mDefaultDialerObserver = invocation.getArgument(1);
             return null;
@@ -223,6 +243,8 @@
                 mSubscriptionManagerAdapter,
                 mSettingsSecureAdapter,
                 mLock);
+        telecomServiceImpl.setTransactionManager(mTransactionManager);
+        telecomServiceImpl.setAnomalyReporterAdapter(mAnomalyReporterAdapter);
         mTSIBinder = telecomServiceImpl.getBinder();
         mComponentContextFixture.setTelecomManager(mTelecomManager);
         when(mTelecomManager.getDefaultDialerPackage()).thenReturn(DEFAULT_DIALER_PACKAGE);
@@ -271,6 +293,51 @@
         assertEquals(SIP_PA_HANDLE_17, returnedHandleSip);
     }
 
+    /**
+     * Clear the groupId from the PhoneAccount if a package does NOT have MODIFY_PHONE_STATE
+     */
+    @SmallTest
+    @Test
+    public void testGroupIdIsClearedWhenPermissionIsMissing() throws RemoteException {
+        // GIVEN
+        PhoneAccount phoneAccount = makePhoneAccount(TEL_PA_HANDLE_CURRENT)
+                .setGroupId("testId")
+                .build();
+        // WHEN
+        doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccount(
+                eq(TEL_PA_HANDLE_CURRENT), any(UserHandle.class), anyBoolean());
+        doNothing().when(mAppOpsManager).checkPackage(anyInt(), anyString());
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingPermission(MODIFY_PHONE_STATE);
+        // THEN
+        PhoneAccount account =
+                mTSIBinder.getPhoneAccount(TEL_PA_HANDLE_CURRENT, PACKAGE_NAME);
+        assertEquals("***", account.getGroupId());
+    }
+
+    /**
+     * Ensure groupId is not cleared if a package has MODIFY_PHONE_STATE
+     */
+    @SmallTest
+    @Test
+    public void testGroupIdIsNotCleared() throws RemoteException {
+        // GIVEN
+        final String groupId = "testId";
+        PhoneAccount phoneAccount = makePhoneAccount(TEL_PA_HANDLE_CURRENT)
+                .setGroupId(groupId)
+                .build();
+        // WHEN
+        doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccount(
+                eq(TEL_PA_HANDLE_CURRENT), any(UserHandle.class), anyBoolean());
+        doNothing().when(mAppOpsManager).checkPackage(anyInt(), anyString());
+        doReturn(PackageManager.PERMISSION_GRANTED)
+                .when(mContext).checkCallingPermission(MODIFY_PHONE_STATE);
+        // THEN
+        PhoneAccount account =
+                mTSIBinder.getPhoneAccount(TEL_PA_HANDLE_CURRENT, DEFAULT_DIALER_PACKAGE);
+        assertEquals(groupId, account.getGroupId());
+    }
+
     @SmallTest
     @Test
     public void testGetDefaultOutgoingPhoneAccountSucceedsIfCallerIsSimCallManager()
@@ -354,19 +421,90 @@
                 .setUserSelectedOutgoingPhoneAccount(eq(TEL_PA_HANDLE_16), any(UserHandle.class));
     }
 
+    @Test
+    public void testAddCallWithOutgoingCall() throws RemoteException {
+        // GIVEN
+        CallAttributes mOutgoingCallAttributes = new CallAttributes.Builder(TEL_PA_HANDLE_CURRENT,
+                CallAttributes.DIRECTION_OUTGOING, TEST_NAME, TEST_URI)
+                .setCallType(CallAttributes.AUDIO_CALL)
+                .setCallCapabilities(CallAttributes.SUPPORTS_SET_INACTIVE)
+                .build();
+        PhoneAccount phoneAccount = makeMultiUserPhoneAccount(TEL_PA_HANDLE_CURRENT).build();
+        phoneAccount.setIsEnabled(true);
+
+        // WHEN
+        when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn(
+                phoneAccount);
+
+        doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccount(
+                eq(TEL_PA_HANDLE_CURRENT), any(UserHandle.class));
+
+        mTSIBinder.addCall(mOutgoingCallAttributes, mICallEventCallback, "1", CALLING_PACKAGE);
+
+        // THEN
+        verify(mTransactionManager, times(1))
+                .addTransaction(isA(OutgoingCallTransaction.class), isA(OutcomeReceiver.class));
+    }
+
+    @Test
+    public void testAddCallWithIncomingCall() throws RemoteException {
+        // GIVEN
+        CallAttributes mIncomingCallAttributes = new CallAttributes.Builder(TEL_PA_HANDLE_CURRENT,
+                CallAttributes.DIRECTION_INCOMING, TEST_NAME, TEST_URI)
+                .setCallType(CallAttributes.AUDIO_CALL)
+                .setCallCapabilities(CallAttributes.SUPPORTS_SET_INACTIVE)
+                .build();
+        PhoneAccount phoneAccount = makeMultiUserPhoneAccount(TEL_PA_HANDLE_CURRENT).build();
+        phoneAccount.setIsEnabled(true);
+
+        // WHEN
+        when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn(
+                phoneAccount);
+
+        doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccount(
+                eq(TEL_PA_HANDLE_CURRENT), any(UserHandle.class));
+
+        mTSIBinder.addCall(mIncomingCallAttributes, mICallEventCallback, "1", CALLING_PACKAGE);
+
+        // THEN
+        verify(mTransactionManager, times(1))
+                .addTransaction(isA(IncomingCallTransaction.class), isA(OutcomeReceiver.class));
+    }
+
+    @Test
+    public void testAddCallWithManagedPhoneAccount() throws RemoteException {
+        // GIVEN
+        CallAttributes attributes = new CallAttributes.Builder(TEL_PA_HANDLE_CURRENT,
+                CallAttributes.DIRECTION_OUTGOING, TEST_NAME, TEST_URI).build();
+        PhoneAccount phoneAccount = makeMultiUserPhoneAccount(TEL_PA_HANDLE_CURRENT)
+                .setCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
+                .build();
+        phoneAccount.setIsEnabled(true);
+
+        // WHEN
+        when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn(
+                phoneAccount);
+
+        doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccount(
+                eq(TEL_PA_HANDLE_CURRENT), any(UserHandle.class));
+
+        // THEN
+        try {
+            mTSIBinder.addCall(attributes, mICallEventCallback, "1", CALLING_PACKAGE);
+            fail("should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // pass
+        }
+    }
+
     @SmallTest
     @Test
-    public void testSetUserSelectedOutgoingPhoneAccountFailure() throws RemoteException {
+    public void testSetUserSelectedOutgoingPhoneAccountWithoutPermission() throws RemoteException {
         doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
                 anyString(), nullable(String.class));
-        try {
-            mTSIBinder.setUserSelectedOutgoingPhoneAccount(TEL_PA_HANDLE_16);
-        } catch (SecurityException e) {
-            // desired result
-        }
-        verify(mFakePhoneAccountRegistrar, never())
-                .setUserSelectedOutgoingPhoneAccount(
-                        any(PhoneAccountHandle.class), any(UserHandle.class));
+
+        assertThrows(SecurityException.class,
+                () -> mTSIBinder.setUserSelectedOutgoingPhoneAccount(TEL_PA_HANDLE_16));
     }
 
     @SmallTest
@@ -378,11 +516,11 @@
         // Returns all phone accounts when getCallCapablePhoneAccounts is called.
         when(mFakePhoneAccountRegistrar
                 .getCallCapablePhoneAccounts(nullable(String.class), eq(true),
-                        nullable(UserHandle.class))).thenReturn(fullPHList);
+                        nullable(UserHandle.class), eq(true))).thenReturn(fullPHList);
         // Returns only enabled phone accounts when getCallCapablePhoneAccounts is called.
         when(mFakePhoneAccountRegistrar
                 .getCallCapablePhoneAccounts(nullable(String.class), eq(false),
-                        nullable(UserHandle.class))).thenReturn(smallPHList);
+                        nullable(UserHandle.class), eq(true))).thenReturn(smallPHList);
         makeAccountsVisibleToAllUsers(TEL_PA_HANDLE_16, SIP_PA_HANDLE_17);
 
         assertEquals(fullPHList,
@@ -395,21 +533,63 @@
 
     @SmallTest
     @Test
-    public void testGetCallCapablePhoneAccountsFailure() throws RemoteException {
+    public void testGetCallCapablePhoneAccountsWithoutPermission() throws RemoteException {
         List<String> enforcedPermissions = List.of(READ_PHONE_STATE, READ_PRIVILEGED_PHONE_STATE);
 
         doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
                 argThat(new AnyStringIn(enforcedPermissions)), anyString());
 
-        List<PhoneAccountHandle> result = null;
-        try {
-            result = mTSIBinder.getCallCapablePhoneAccounts(true, "", null).getList();
-        } catch (SecurityException e) {
-            // intended behavior
-        }
-        assertNull(result);
-        verify(mFakePhoneAccountRegistrar, never())
-                .getCallCapablePhoneAccounts(anyString(), anyBoolean(), any(UserHandle.class));
+        assertThrows(SecurityException.class,
+                () -> mTSIBinder.getCallCapablePhoneAccounts(true, "", null));
+    }
+
+    @SmallTest
+    @Test
+    public void testGetSelfManagedPhoneAccounts() throws RemoteException {
+        List<PhoneAccountHandle> accounts = List.of(TEL_PA_HANDLE_16);
+
+        when(mFakePhoneAccountRegistrar.getSelfManagedPhoneAccounts(nullable(UserHandle.class)))
+                .thenReturn(accounts);
+        makeAccountsVisibleToAllUsers(TEL_PA_HANDLE_16);
+
+        assertEquals(accounts,
+                mTSIBinder.getSelfManagedPhoneAccounts(DEFAULT_DIALER_PACKAGE, null).getList());
+    }
+
+    @SmallTest
+    @Test
+    public void testGetSelfManagedPhoneAccountsWithoutPermission() throws RemoteException {
+        List<String> enforcedPermissions = List.of(READ_PHONE_STATE, READ_PRIVILEGED_PHONE_STATE);
+        doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
+                argThat(new AnyStringIn(enforcedPermissions)), anyString());
+
+        assertThrows(SecurityException.class,
+                () -> mTSIBinder.getSelfManagedPhoneAccounts("", null));
+    }
+
+    @SmallTest
+    @Test
+    public void testGetOwnSelfManagedPhoneAccounts() throws RemoteException {
+        List<PhoneAccountHandle> accounts = List.of(TEL_PA_HANDLE_16);
+
+        when(mFakePhoneAccountRegistrar.getSelfManagedPhoneAccountsForPackage(
+                eq(DEFAULT_DIALER_PACKAGE), nullable(UserHandle.class)))
+                .thenReturn(accounts);
+        makeAccountsVisibleToAllUsers(TEL_PA_HANDLE_16);
+
+        assertEquals(accounts,
+                mTSIBinder.getOwnSelfManagedPhoneAccounts(DEFAULT_DIALER_PACKAGE, null).getList());
+    }
+
+    @SmallTest
+    @Test
+    public void testGetOwnSelfManagedPhoneAccountsWithoutPermission() throws RemoteException {
+        List<String> enforcedPermissions = List.of(MANAGE_OWN_CALLS);
+        doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
+                argThat(new AnyStringIn(enforcedPermissions)), anyString());
+
+        assertThrows(SecurityException.class,
+                () -> mTSIBinder.getOwnSelfManagedPhoneAccounts("", null));
     }
 
     @SmallTest
@@ -419,10 +599,12 @@
         List<PhoneAccountHandle> telPHList = List.of(TEL_PA_HANDLE_16);
 
         when(mFakePhoneAccountRegistrar
-                .getCallCapablePhoneAccounts(eq("tel"), anyBoolean(), any(UserHandle.class)))
+                .getCallCapablePhoneAccounts(eq("tel"), anyBoolean(),
+                        any(UserHandle.class), anyBoolean()))
                 .thenReturn(telPHList);
         when(mFakePhoneAccountRegistrar
-                .getCallCapablePhoneAccounts(eq("sip"), anyBoolean(), any(UserHandle.class)))
+                .getCallCapablePhoneAccounts(eq("sip"), anyBoolean(),
+                        any(UserHandle.class), anyBoolean()))
                 .thenReturn(sipPHList);
         makeAccountsVisibleToAllUsers(TEL_PA_HANDLE_16, SIP_PA_HANDLE_17);
 
@@ -436,11 +618,21 @@
 
     @SmallTest
     @Test
+    public void testGetPhoneAccountsSupportingSchemeWithoutPermission() throws RemoteException {
+        List<String> enforcedPermissions = List.of(MODIFY_PHONE_STATE);
+        doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
+                argThat(new AnyStringIn(enforcedPermissions)), anyString());
+
+        assertTrue(mTSIBinder.getPhoneAccountsSupportingScheme("any", "").getList().isEmpty());
+    }
+
+    @SmallTest
+    @Test
     public void testGetPhoneAccountsForPackage() throws RemoteException {
         List<PhoneAccountHandle> phoneAccountHandleList = List.of(
             TEL_PA_HANDLE_16, SIP_PA_HANDLE_17);
         when(mFakePhoneAccountRegistrar
-                .getPhoneAccountsForPackage(anyString(), any(UserHandle.class)))
+                .getAllPhoneAccountHandlesForPackage(any(UserHandle.class), anyString()))
                 .thenReturn(phoneAccountHandleList);
         makeAccountsVisibleToAllUsers(TEL_PA_HANDLE_16, SIP_PA_HANDLE_17);
         assertEquals(phoneAccountHandleList,
@@ -450,7 +642,20 @@
 
     @SmallTest
     @Test
+    public void testGetPhoneAccountsForPackageWithoutPermission() throws RemoteException {
+        List<String> enforcedPermissions = List.of(READ_PRIVILEGED_PHONE_STATE);
+        doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
+                argThat(new AnyStringIn(enforcedPermissions)), any());
+
+        assertThrows(SecurityException.class,
+                () -> mTSIBinder.getPhoneAccountsForPackage(""));
+    }
+
+    @SmallTest
+    @Test
     public void testGetPhoneAccount() throws Exception {
+        doReturn(PackageManager.PERMISSION_GRANTED)
+                .when(mContext).checkCallingPermission(MODIFY_PHONE_STATE);
         makeAccountsVisibleToAllUsers(TEL_PA_HANDLE_16, SIP_PA_HANDLE_17);
         assertEquals(TEL_PA_HANDLE_16, mTSIBinder.getPhoneAccount(TEL_PA_HANDLE_16,
                 mContext.getPackageName()).getAccountHandle());
@@ -466,15 +671,96 @@
 
     @SmallTest
     @Test
+    public void testGetAllPhoneAccountsCount() throws RemoteException {
+        List<PhoneAccount> phoneAccountList = List.of(
+                makePhoneAccount(TEL_PA_HANDLE_16).build(),
+                makePhoneAccount(SIP_PA_HANDLE_17).build());
+
+        when(mFakePhoneAccountRegistrar.getAllPhoneAccounts(any(UserHandle.class), anyBoolean()))
+                .thenReturn(phoneAccountList);
+
+        assertEquals(phoneAccountList.size(), mTSIBinder.getAllPhoneAccountsCount());
+    }
+
+    @SmallTest
+    @Test
+    public void testGetAllPhoneAccountsCountWithoutPermission() throws RemoteException {
+        List<String> enforcedPermissions = List.of(MODIFY_PHONE_STATE);
+        doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
+                argThat(new AnyStringIn(enforcedPermissions)), any());
+
+        assertThrows(SecurityException.class,
+                () -> mTSIBinder.getAllPhoneAccountsCount());
+    }
+
+    @SmallTest
+    @Test
     public void testGetAllPhoneAccounts() throws RemoteException {
         List<PhoneAccount> phoneAccountList = List.of(
                 makePhoneAccount(TEL_PA_HANDLE_16).build(),
                 makePhoneAccount(SIP_PA_HANDLE_17).build());
 
-        when(mFakePhoneAccountRegistrar.getAllPhoneAccounts(any(UserHandle.class)))
+        when(mFakePhoneAccountRegistrar.getAllPhoneAccounts(any(UserHandle.class), anyBoolean()))
                 .thenReturn(phoneAccountList);
 
-        assertEquals(2, mTSIBinder.getAllPhoneAccounts().getList().size());
+        assertEquals(phoneAccountList.size(), mTSIBinder.getAllPhoneAccounts().getList().size());
+    }
+
+    @SmallTest
+    @Test
+    public void testGetAllPhoneAccountsWithoutPermission() throws RemoteException {
+        List<String> enforcedPermissions = List.of(MODIFY_PHONE_STATE);
+        doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
+                argThat(new AnyStringIn(enforcedPermissions)), any());
+
+        assertThrows(SecurityException.class,
+                () -> mTSIBinder.getAllPhoneAccounts());
+    }
+
+    @SmallTest
+    @Test
+    public void testGetAllPhoneAccountHandles() throws RemoteException {
+        List<PhoneAccountHandle> handles = List.of(TEL_PA_HANDLE_16, SIP_PA_HANDLE_17);
+        when(mFakePhoneAccountRegistrar.getAllPhoneAccountHandles(
+                any(UserHandle.class), anyBoolean())).thenReturn(handles);
+
+        assertEquals(handles, mTSIBinder.getAllPhoneAccountHandles().getList());
+    }
+
+    @SmallTest
+    @Test
+    public void testGetAllPhoneAccountHandlesWithoutPermission() throws RemoteException {
+        List<String> enforcedPermissions = List.of(MODIFY_PHONE_STATE);
+        doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
+                argThat(new AnyStringIn(enforcedPermissions)), any());
+
+        assertThrows(SecurityException.class,
+                () -> mTSIBinder.getAllPhoneAccountHandles());
+    }
+
+    @SmallTest
+    @Test
+    public void testGetSimCallManager() throws RemoteException {
+        final PhoneAccountHandle handle = TEL_PA_HANDLE_16;
+        final int subId = 1;
+        when(mFakePhoneAccountRegistrar.getSimCallManager(eq(subId), any(UserHandle.class)))
+                .thenReturn(handle);
+
+        assertEquals(handle, mTSIBinder.getSimCallManager(subId, "any"));
+    }
+
+    @SmallTest
+    @Test
+    public void testGetSimCallManagerForUser() throws RemoteException {
+        final PhoneAccountHandle handle = TEL_PA_HANDLE_16;
+        final int user = 1;
+        when(mFakePhoneAccountRegistrar.getSimCallManager(
+                argThat(userHandle -> {
+                    return userHandle.getIdentifier() == user;
+                })))
+                .thenReturn(handle);
+
+        assertEquals(handle, mTSIBinder.getSimCallManagerForUser(user, "any"));
     }
 
     @SmallTest
@@ -492,6 +778,65 @@
 
     @SmallTest
     @Test
+    public void testRegisterPhoneAccountWithoutPermissionAnomalyReported() throws RemoteException {
+        PhoneAccountHandle handle = new PhoneAccountHandle(
+                new ComponentName("package", "cs"), "test", Binder.getCallingUserHandle());
+        PhoneAccount account = makeSelfManagedPhoneAccount(handle).build();
+
+        List<String> enforcedPermissions = List.of(MANAGE_OWN_CALLS);
+        doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
+                argThat(new AnyStringIn(enforcedPermissions)), any());
+
+        registerPhoneAccountTestHelper(account, false);
+        verify(mAnomalyReporterAdapter).reportAnomaly(
+                TelecomServiceImpl.REGISTER_PHONE_ACCOUNT_ERROR_UUID,
+                TelecomServiceImpl.REGISTER_PHONE_ACCOUNT_ERROR_MSG);
+    }
+
+    @SmallTest
+    @Test
+    public void testRegisterPhoneAccountSelfManagedWithoutPermission() throws RemoteException {
+        PhoneAccountHandle handle = new PhoneAccountHandle(
+                new ComponentName("package", "cs"), "test", Binder.getCallingUserHandle());
+        PhoneAccount account = makeSelfManagedPhoneAccount(handle).build();
+
+        List<String> enforcedPermissions = List.of(MANAGE_OWN_CALLS);
+        doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
+                argThat(new AnyStringIn(enforcedPermissions)), any());
+
+        registerPhoneAccountTestHelper(account, false);
+    }
+
+    @SmallTest
+    @Test
+    public void testRegisterPhoneAccountSelfManagedInvalidCapabilities() throws RemoteException {
+        PhoneAccountHandle handle = new PhoneAccountHandle(
+                new ComponentName("package", "cs"), "test", Binder.getCallingUserHandle());
+
+        PhoneAccount selfManagedCallProviderAccount = makePhoneAccount(handle)
+                .setCapabilities(
+                    PhoneAccount.CAPABILITY_SELF_MANAGED |
+                    PhoneAccount.CAPABILITY_CALL_PROVIDER)
+                .build();
+        registerPhoneAccountTestHelper(selfManagedCallProviderAccount, false);
+
+        PhoneAccount selfManagedConnectionManagerAccount = makePhoneAccount(handle)
+                .setCapabilities(
+                    PhoneAccount.CAPABILITY_SELF_MANAGED |
+                    PhoneAccount.CAPABILITY_CONNECTION_MANAGER)
+                .build();
+        registerPhoneAccountTestHelper(selfManagedConnectionManagerAccount, false);
+
+        PhoneAccount selfManagedSimSubscriptionAccount = makePhoneAccount(handle)
+                .setCapabilities(
+                    PhoneAccount.CAPABILITY_SELF_MANAGED |
+                    PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
+                .build();
+        registerPhoneAccountTestHelper(selfManagedSimSubscriptionAccount, false);
+    }
+
+    @SmallTest
+    @Test
     public void testRegisterPhoneAccountWithoutModifyPermission() throws RemoteException {
         // tests the case where the package does not have MODIFY_PHONE_STATE but is
         // registering its own phone account as a third-party connection service
@@ -589,7 +934,7 @@
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
         boolean didExceptionOccur = false;
         try {
-            mTSIBinder.registerPhoneAccount(testPhoneAccount);
+            mTSIBinder.registerPhoneAccount(testPhoneAccount, CALLING_PACKAGE);
         } catch (Exception e) {
             didExceptionOccur = true;
         }
@@ -606,6 +951,26 @@
 
     @SmallTest
     @Test
+    public void testRegisterPhoneAccountImageIconCrossUser() throws RemoteException {
+        String packageNameToUse = "com.android.officialpackage";
+        PhoneAccountHandle phHandle = new PhoneAccountHandle(new ComponentName(
+                packageNameToUse, "cs"), "test", Binder.getCallingUserHandle());
+        Icon icon = Icon.createWithContentUri("content://10@media/external/images/media/");
+        PhoneAccount phoneAccount = makePhoneAccount(phHandle).setIcon(icon).build();
+        doReturn(PackageManager.PERMISSION_GRANTED)
+                .when(mContext).checkCallingOrSelfPermission(MODIFY_PHONE_STATE);
+
+        // This should fail; security exception will be thrown.
+        registerPhoneAccountTestHelper(phoneAccount, false);
+
+        icon = Icon.createWithContentUri("content://0@media/external/images/media/");
+        phoneAccount = makePhoneAccount(phHandle).setIcon(icon).build();
+        // This should succeed.
+        registerPhoneAccountTestHelper(phoneAccount, true);
+    }
+
+    @SmallTest
+    @Test
     public void testUnregisterPhoneAccount() throws RemoteException {
         String packageNameToUse = "com.android.officialpackage";
         PhoneAccountHandle phHandle = new PhoneAccountHandle(new ComponentName(
@@ -615,7 +980,7 @@
         doReturn(PackageManager.PERMISSION_GRANTED)
                 .when(mContext).checkCallingOrSelfPermission(MODIFY_PHONE_STATE);
 
-        mTSIBinder.unregisterPhoneAccount(phHandle);
+        mTSIBinder.unregisterPhoneAccount(phHandle, CALLING_PACKAGE);
         verify(mFakePhoneAccountRegistrar).unregisterPhoneAccount(phHandle);
     }
 
@@ -632,7 +997,7 @@
         when(pm.hasSystemFeature(PackageManager.FEATURE_TELECOM)).thenReturn(false);
 
         try {
-            mTSIBinder.unregisterPhoneAccount(phHandle);
+            mTSIBinder.unregisterPhoneAccount(phHandle, CALLING_PACKAGE);
         } catch (UnsupportedOperationException e) {
             // expected behavior
         }
@@ -644,25 +1009,47 @@
 
     @SmallTest
     @Test
+    public void testClearAccounts() throws RemoteException {
+        mTSIBinder.clearAccounts(CALLING_PACKAGE);
+
+        verify(mFakePhoneAccountRegistrar)
+                .clearAccounts(CALLING_PACKAGE, mTSIBinder.getCallingUserHandle());
+    }
+
+    @SmallTest
+    @Test
+    public void testClearAccountsWithoutPermission() throws RemoteException {
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingOrSelfPermission(MODIFY_PHONE_STATE);
+
+        assertThrows(UnsupportedOperationException.class,
+                () -> mTSIBinder.clearAccounts(CALLING_PACKAGE));
+    }
+
+    @SmallTest
+    @Test
     public void testAddNewIncomingCall() throws Exception {
-        PhoneAccount phoneAccount = makePhoneAccount(TEL_PA_HANDLE_CURRENT).build();
+        PhoneAccount phoneAccount = makePhoneAccount(TEL_PA_HANDLE_16).build();
         phoneAccount.setIsEnabled(true);
         doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccount(
-                eq(TEL_PA_HANDLE_CURRENT), any(UserHandle.class));
+                eq(TEL_PA_HANDLE_16), any(UserHandle.class));
         doNothing().when(mAppOpsManager).checkPackage(anyInt(), anyString());
         Bundle extras = createSampleExtras();
 
-        mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_CURRENT, extras);
+        mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_16, extras, CALLING_PACKAGE);
 
+        verify(mFakePhoneAccountRegistrar).getPhoneAccount(
+                TEL_PA_HANDLE_16, TEL_PA_HANDLE_16.getUserHandle());
         addCallTestHelper(TelecomManager.ACTION_INCOMING_CALL,
-                CallIntentProcessor.KEY_IS_INCOMING_CALL, extras, false);
+                CallIntentProcessor.KEY_IS_INCOMING_CALL, extras,
+                TEL_PA_HANDLE_16, false);
     }
 
     @SmallTest
     @Test
     public void testAddNewIncomingCallFailure() throws Exception {
         try {
-            mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_16, null);
+            mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_16, null, CALLING_PACKAGE);
         } catch (SecurityException e) {
             // expected
         }
@@ -670,7 +1057,7 @@
         doThrow(new SecurityException()).when(mAppOpsManager).checkPackage(anyInt(), anyString());
 
         try {
-            mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_CURRENT, null);
+            mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_CURRENT, null, CALLING_PACKAGE);
         } catch (SecurityException e) {
             // expected
         }
@@ -693,7 +1080,7 @@
         mTSIBinder.addNewUnknownCall(TEL_PA_HANDLE_CURRENT, extras);
 
         addCallTestHelper(TelecomManager.ACTION_NEW_UNKNOWN_CALL,
-                CallIntentProcessor.KEY_IS_UNKNOWN_CALL, extras, true);
+                CallIntentProcessor.KEY_IS_UNKNOWN_CALL, extras, TEL_PA_HANDLE_CURRENT, true);
     }
 
     @SmallTest
@@ -719,7 +1106,8 @@
     }
 
     private void addCallTestHelper(String expectedAction, String extraCallKey,
-            Bundle expectedExtras, boolean isUnknown) {
+            Bundle expectedExtras, PhoneAccountHandle expectedPhoneAccountHandle,
+            boolean isUnknown) {
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
         if (isUnknown) {
             verify(mCallIntentProcessorAdapter).processUnknownCallIntent(any(CallsManager.class),
@@ -731,7 +1119,7 @@
         Intent capturedIntent = intentCaptor.getValue();
         assertEquals(expectedAction, capturedIntent.getAction());
         Bundle intentExtras = capturedIntent.getExtras();
-        assertEquals(TEL_PA_HANDLE_CURRENT,
+        assertEquals(expectedPhoneAccountHandle,
                 intentExtras.get(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE));
         assertTrue(intentExtras.getBoolean(extraCallKey));
 
@@ -747,12 +1135,18 @@
         }
     }
 
+    /**
+     * Place a managed call with no PhoneAccount specified and ensure no security exception is
+     * thrown.
+     */
     @SmallTest
     @Test
     public void testPlaceCallWithNonEmergencyPermission() throws Exception {
         Uri handle = Uri.parse("tel:6505551234");
         Bundle extras = createSampleExtras();
 
+        // We have passed in the DEFAULT_DIALER_PACKAGE for this test, so canCallPhone is always
+        // true.
         when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(),
                 nullable(String.class), nullable(String.class)))
                 .thenReturn(AppOpsManager.MODE_ALLOWED);
@@ -762,15 +1156,339 @@
                 .when(mContext).checkCallingPermission(CALL_PRIVILEGED);
 
         mTSIBinder.placeCall(handle, extras, DEFAULT_DIALER_PACKAGE, null);
-        placeCallTestHelper(handle, extras, true);
+        placeCallTestHelper(handle, extras, /*isSelfManagedExpected*/ false,
+                /*shouldNonEmergencyBeAllowed*/ true);
     }
 
+    /**
+     * Ensure that we get a SecurityException if the UID of the caller doesn't match the UID of the
+     * UID of the package name passed in.
+     */
+    @SmallTest
+    @Test
+    public void testPlaceCall_enforceCallingPackageFailure() throws Exception {
+        Uri handle = Uri.parse("tel:6505551234");
+        Bundle extras = createSampleExtras();
+        // callingPackage matches the PhoneAccountHandle, so this is an app with a self-managed
+        // ConnectionService.
+        extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT);
+
+        // Return a non-matching UID for testing purposes.
+        when(mPackageManager.getPackageUid(anyString(), eq(0))).thenReturn(-1);
+        try {
+            mTSIBinder.placeCall(handle, extras, PACKAGE_NAME, null);
+            fail("Expected SecurityException because calling package doesn't match");
+        } catch(SecurityException e) {
+            // expected
+        }
+    }
+
+    /**
+     * In the case that there is a self-managed call request and MANAGE_OWN_CALLS is granted, ensure
+     * that placeCall does not generate a SecurityException.
+     */
+    @SmallTest
+    @Test
+    public void testPlaceCall_selfManaged_permissionGranted() throws Exception {
+        doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer(
+                eq(DEFAULT_DIALER_PACKAGE), anyInt());
+        when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn(
+                makeSelfManagedPhoneAccount(TEL_PA_HANDLE_CURRENT).build());
+        Uri handle = Uri.parse("tel:6505551234");
+        Bundle extras = createSampleExtras();
+        // callingPackage matches the PhoneAccountHandle, so this is an app with a self-managed
+        // ConnectionService.
+        extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT);
+
+        // pass MANAGE_OWN_CALLS check, but do not have CALL_PHONE
+        doNothing().when(mContext).enforceCallingOrSelfPermission(
+                eq(Manifest.permission.MANAGE_OWN_CALLS), anyString());
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingPermission(CALL_PHONE);
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingPermission(CALL_PRIVILEGED);
+
+        try {
+            mTSIBinder.placeCall(handle, extras, PACKAGE_NAME, null);
+            placeCallTestHelper(handle, extras, /*isSelfManagedExpected*/ true,
+                    /*shouldNonEmergencyBeAllowed*/ false);
+        } catch(SecurityException e) {
+            fail("Unexpected SecurityException - MANAGE_OWN_CALLS is set");
+        }
+    }
+
+    /**
+     * In the case that the placeCall API is being used place a self-managed call
+     * (phone account is marked self-managed and the calling application owns that PhoneAccount),
+     * ensure that the call gets placed as not self-managed as to not disclose PA info.
+     */
+    @SmallTest
+    @Test
+    public void testPlaceCall_selfManaged_noPermission() throws Exception {
+        doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer(
+                eq(DEFAULT_DIALER_PACKAGE), anyInt());
+        when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn(
+                makeSelfManagedPhoneAccount(TEL_PA_HANDLE_CURRENT).build());
+        Uri handle = Uri.parse("tel:6505551234");
+        Bundle extras = createSampleExtras();
+        // callingPackage matches the PhoneAccountHandle, so this is an app with a self-managed
+        // ConnectionService.
+        extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT);
+
+        doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
+                eq(Manifest.permission.MANAGE_OWN_CALLS), anyString());
+
+        try {
+            mTSIBinder.placeCall(handle, extras, PACKAGE_NAME, null);
+            fail("Expected SecurityException because MANAGE_OWN_CALLS is not set");
+        } catch(SecurityException e) {
+            // expected
+        }
+    }
+
+    /**
+     * In the case that there is a self-managed call request and the app doesn't own that
+     * PhoneAccount, we will need to check CALL_PHONE. If they do not have CALL_PHONE permission,
+     * we need to throw a security exception.
+     */
+    @SmallTest
+    @Test
+    public void testPlaceCall_selfManaged_permissionFail() throws Exception {
+        doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer(
+                eq(DEFAULT_DIALER_PACKAGE), anyInt());
+        when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn(
+                makeSelfManagedPhoneAccount(TEL_PA_HANDLE_CURRENT).build());
+        Uri handle = Uri.parse("tel:6505551234");
+        Bundle extras = createSampleExtras();
+        // callingPackage doesn't match the PhoneAccountHandle package
+        extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT);
+
+        // pass MANAGE_OWN_CALLS check, but do not have CALL PHONE
+        doNothing().when(mContext).enforceCallingOrSelfPermission(
+                eq(Manifest.permission.MANAGE_OWN_CALLS), anyString());
+        doThrow(new SecurityException())
+                .when(mContext).enforceCallingOrSelfPermission(eq(CALL_PHONE), anyString());
+
+        try {
+            // Calling package is received and is not the same as PACKAGE_NAME
+            mTSIBinder.placeCall(handle, extras, PACKAGE_NAME + "2", null);
+            fail("Expected a SecurityException - CALL_PHONE was not granted");
+        } catch(SecurityException e) {
+            // expected
+        }
+    }
+
+    /**
+     * In the case that there is a self-managed call request and the app doesn't own that
+     * PhoneAccount, we will need to check CALL_PHONE. If they have the CALL_PHONE permission, but
+     * the app op has been denied, this should throw a security exception.
+     */
+    @SmallTest
+    @Test
+    public void testPlaceCall_selfManaged_appOpPermissionFail() throws Exception {
+        doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer(
+                eq(DEFAULT_DIALER_PACKAGE), anyInt());
+        when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn(
+                makeSelfManagedPhoneAccount(TEL_PA_HANDLE_CURRENT).build());
+        Uri handle = Uri.parse("tel:6505551234");
+        Bundle extras = createSampleExtras();
+        // callingPackage doesn't match the PhoneAccountHandle package.
+        extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT);
+
+        // pass MANAGE_OWN_CALLS check, but do not have CALL PHONE
+        doNothing().when(mContext).enforceCallingOrSelfPermission(
+                eq(Manifest.permission.MANAGE_OWN_CALLS), anyString());
+        doNothing().when(mContext).enforceCallingOrSelfPermission(eq(CALL_PHONE), anyString());
+        when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(),
+                nullable(String.class), nullable(String.class)))
+                .thenReturn(AppOpsManager.MODE_ERRORED);
+        try {
+            mTSIBinder.placeCall(handle, extras, PACKAGE_NAME + "2", null);
+            fail("Expected a SecurityException - CALL_PHONE app op is denied");
+        } catch(SecurityException e) {
+            // expected
+        }
+    }
+
+    /**
+     * In the case that there is a self-managed call request and the app doesn't own that
+     * PhoneAccount, we will need to check CALL_PHONE. If they have the correct permissions, the
+     * call will go through, however we will have removed the self-managed PhoneAccountHandle. The
+     * call will go through as a normal managed call request with no PhoneAccountHandle.
+     */
+    @SmallTest
+    @Test
+    public void testPlaceCall_selfManaged_differentCallingPackage() throws Exception {
+        doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer(
+                eq(DEFAULT_DIALER_PACKAGE), anyInt());
+        when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn(
+                makeSelfManagedPhoneAccount(TEL_PA_HANDLE_CURRENT).build());
+        Uri handle = Uri.parse("tel:6505551234");
+        Bundle extras = createSampleExtras();
+        // callingPackage doesn't match the PhoneAccountHandle package
+        extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT);
+
+        // simulate default dialer so CALL_PHONE is granted.
+        when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(),
+                nullable(String.class), nullable(String.class)))
+                .thenReturn(AppOpsManager.MODE_ALLOWED);
+        doReturn(PackageManager.PERMISSION_GRANTED)
+                .when(mContext).checkCallingPermission(CALL_PHONE);
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingPermission(CALL_PRIVILEGED);
+
+        // We expect the call to go through with no PhoneAccount specified, since the request
+        // contained a self-managed PhoneAccountHandle that didn't belong to this app.
+        Bundle expectedExtras = extras.deepCopy();
+        expectedExtras.remove(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
+        try {
+            mTSIBinder.placeCall(handle, extras, DEFAULT_DIALER_PACKAGE, null);
+        } catch (SecurityException e) {
+            fail("Unexpected SecurityException - CTS is default dialer and MANAGE_OWN_CALLS is not"
+                    + " required. Exception: " + e);
+        }
+        placeCallTestHelper(handle, extras, /*isSelfManagedExpected*/ false,
+                /*shouldNonEmergencyBeAllowed*/ true);
+    }
+
+    /**
+     * In the case that there is a managed call request and the app owns that
+     * PhoneAccount (but is not a self-managed), we will still need to check CALL_PHONE.
+     */
+    @SmallTest
+    @Test
+    public void testPlaceCall_samePackage_managedPhoneAccount_permissionFail() throws Exception {
+        doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer(
+                eq(DEFAULT_DIALER_PACKAGE), anyInt());
+        when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn(
+                makePhoneAccount(TEL_PA_HANDLE_CURRENT).build());
+        Uri handle = Uri.parse("tel:6505551234");
+        Bundle extras = createSampleExtras();
+        // callingPackage doesn't match the PhoneAccountHandle package
+        extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT);
+
+        // CALL_PHONE is not granted to the device.
+        doThrow(new SecurityException())
+                .when(mContext).enforceCallingOrSelfPermission(
+                        eq(Manifest.permission.MANAGE_OWN_CALLS), anyString());
+        doThrow(new SecurityException())
+                .when(mContext).enforceCallingOrSelfPermission(eq(CALL_PHONE), anyString());
+
+        try {
+            mTSIBinder.placeCall(handle, extras, PACKAGE_NAME + "2", null);
+            fail("Expected a SecurityException - CALL_PHONE is not granted");
+        } catch(SecurityException e) {
+            // expected
+        }
+    }
+
+    /**
+     * In the case that there is a managed call request and the app owns that
+     * PhoneAccount (but is not a self-managed), we will still need to check CALL_PHONE.
+     */
+    @SmallTest
+    @Test
+    public void testPlaceCall_samePackage_managedPhoneAccount_AppOpFail() throws Exception {
+        doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer(
+                eq(DEFAULT_DIALER_PACKAGE), anyInt());
+        when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn(
+                makePhoneAccount(TEL_PA_HANDLE_CURRENT).build());
+        Uri handle = Uri.parse("tel:6505551234");
+        Bundle extras = createSampleExtras();
+        // callingPackage matches the PhoneAccountHandle, but this is not a self managed phone
+        // account.
+        extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT);
+
+        // CALL_PHONE is granted, but the app op is not
+        doThrow(new SecurityException())
+                .when(mContext).enforceCallingOrSelfPermission(
+                        eq(Manifest.permission.MANAGE_OWN_CALLS), anyString());
+        doNothing().when(mContext).enforceCallingOrSelfPermission(eq(CALL_PHONE), anyString());
+        when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(),
+                nullable(String.class), nullable(String.class)))
+                .thenReturn(AppOpsManager.MODE_ERRORED);
+
+        try {
+            mTSIBinder.placeCall(handle, extras, PACKAGE_NAME + "2", null);
+            fail("Expected a SecurityException - CALL_PHONE app op is denied");
+        } catch(SecurityException e) {
+            // expected
+        }
+    }
+
+    /**
+     * Since this is a self-managed call being requested, so ensure we report the call as
+     * self-managed and without non-emergency permissions.
+     */
+    @SmallTest
+    @Test
+    public void testPlaceCall_selfManaged_nonEmergencyPermission() throws Exception {
+        doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer(
+                eq(DEFAULT_DIALER_PACKAGE), anyInt());
+        when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn(
+                makeSelfManagedPhoneAccount(TEL_PA_HANDLE_CURRENT).build());
+        Uri handle = Uri.parse("tel:6505551234");
+        Bundle extras = createSampleExtras();
+        // callingPackage matches the PhoneAccountHandle, so this is an app with a self-managed
+        // ConnectionService.
+        extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT);
+
+        // enforceCallingOrSelfPermission is implicitly granted for MANAGE_OWN_CALLS here and
+        // CALL_PHONE is not required.
+        when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(),
+                nullable(String.class), nullable(String.class)))
+                .thenReturn(AppOpsManager.MODE_IGNORED);
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingPermission(CALL_PHONE);
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingPermission(CALL_PRIVILEGED);
+
+        mTSIBinder.placeCall(handle, extras, PACKAGE_NAME, null);
+        placeCallTestHelper(handle, extras, /*isSelfManagedExpected*/ true,
+                /*shouldNonEmergencyBeAllowed*/ false);
+    }
+
+    /**
+     * Default dialer is calling placeCall and has CALL_PHONE granted, so non-emergency calls
+     * are allowed.
+     */
+    @SmallTest
+    @Test
+    public void testPlaceCall_managed_nonEmergencyGranted() throws Exception {
+        doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer(
+                eq(DEFAULT_DIALER_PACKAGE), anyInt());
+        Uri handle = Uri.parse("tel:6505551234");
+        Bundle extras = createSampleExtras();
+        // callingPackage doesn't match the PhoneAccountHandle, so this app does not have a
+        // self-managed ConnectionService
+        extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, TEL_PA_HANDLE_CURRENT);
+
+        // CALL_PHONE granted
+        when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(),
+                nullable(String.class), nullable(String.class)))
+                .thenReturn(AppOpsManager.MODE_ALLOWED);
+        doReturn(PackageManager.PERMISSION_GRANTED)
+                .when(mContext).checkCallingPermission(CALL_PHONE);
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingPermission(CALL_PRIVILEGED);
+
+        mTSIBinder.placeCall(handle, extras, DEFAULT_DIALER_PACKAGE, null);
+        placeCallTestHelper(handle, extras, /*isSelfManagedExpected*/ false,
+                /*shouldNonEmergencyBeAllowed*/ true);
+    }
+
+    /**
+     * The default dialer is requesting to place a call and CALL_PHONE is granted, however
+     * OP_CALL_PHONE app op is denied to that app, so non-emergency calls will be denied.
+     */
     @SmallTest
     @Test
     public void testPlaceCallWithAppOpsOff() throws Exception {
         Uri handle = Uri.parse("tel:6505551234");
         Bundle extras = createSampleExtras();
 
+        // We have passed in the DEFAULT_DIALER_PACKAGE for this test, so canCallPhone is always
+        // true.
         when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(),
                 nullable(String.class), nullable(String.class)))
                 .thenReturn(AppOpsManager.MODE_IGNORED);
@@ -780,15 +1498,21 @@
                 .when(mContext).checkCallingPermission(CALL_PRIVILEGED);
 
         mTSIBinder.placeCall(handle, extras, DEFAULT_DIALER_PACKAGE, null);
-        placeCallTestHelper(handle, extras, false);
+        placeCallTestHelper(handle, extras, /*isSelfManagedExpected*/ false,
+                /*shouldNonEmergencyBeAllowed*/ false);
     }
 
+    /**
+     * The default dialer is requesting to place a call, however CALL_PHONE is denied to that app,
+     * so non-emergency calls will be denied.
+     */
     @SmallTest
     @Test
     public void testPlaceCallWithNoCallingPermission() throws Exception {
         Uri handle = Uri.parse("tel:6505551234");
         Bundle extras = createSampleExtras();
 
+        // We are assumed to be default dialer in this test, so canCallPhone is always true.
         when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(),
                 nullable(String.class), nullable(String.class)))
                 .thenReturn(AppOpsManager.MODE_ALLOWED);
@@ -798,37 +1522,82 @@
                 .when(mContext).checkCallingPermission(CALL_PRIVILEGED);
 
         mTSIBinder.placeCall(handle, extras, DEFAULT_DIALER_PACKAGE, null);
-        placeCallTestHelper(handle, extras, false);
+        placeCallTestHelper(handle, extras, /*isSelfManagedExpected*/ false,
+                /*shouldNonEmergencyBeAllowed*/ false);
     }
 
+    /**
+     * Ensure the expected handle, extras, and non-emergency call permission checks have been
+     * correctly included in the ACTION_CALL intent as part of the
+     * {@link UserCallIntentProcessor#processIntent} method called during the placeCall procedure.
+     * @param expectedHandle Expected outgoing number handle
+     * @param expectedExtras Expected extras in the ACTION_CALL intent.
+     * @param shouldNonEmergencyBeAllowed true if non-emergency calls should be allowed, false if
+     *                                    permission checks failed for non-emergency.
+     */
     private void placeCallTestHelper(Uri expectedHandle, Bundle expectedExtras,
-            boolean shouldNonEmergencyBeAllowed) {
+            boolean isSelfManagedExpected, boolean shouldNonEmergencyBeAllowed) {
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mUserCallIntentProcessor).processIntent(intentCaptor.capture(), anyString(),
-                eq(shouldNonEmergencyBeAllowed), eq(true));
+                eq(isSelfManagedExpected), eq(shouldNonEmergencyBeAllowed), eq(true));
         Intent capturedIntent = intentCaptor.getValue();
         assertEquals(Intent.ACTION_CALL, capturedIntent.getAction());
         assertEquals(expectedHandle, capturedIntent.getData());
         assertTrue(areBundlesEqual(expectedExtras, capturedIntent.getExtras()));
     }
 
+    /**
+     * Ensure that if the caller was never granted CALL_PHONE (and is not the default dialer), a
+     * SecurityException is thrown.
+     */
     @SmallTest
     @Test
     public void testPlaceCallFailure() throws Exception {
         Uri handle = Uri.parse("tel:6505551234");
         Bundle extras = createSampleExtras();
 
+        // The app is not considered a privileged dialer and does not have the CALL_PHONE
+        // permission.
         doThrow(new SecurityException())
                 .when(mContext).enforceCallingOrSelfPermission(eq(CALL_PHONE), anyString());
 
         try {
             mTSIBinder.placeCall(handle, extras, "arbitrary_package_name", null);
+            fail("Expected SecurityException because CALL_PHONE was not granted to caller");
         } catch (SecurityException e) {
             // expected
         }
 
         verify(mUserCallIntentProcessor, never())
-                .processIntent(any(Intent.class), anyString(), anyBoolean(), eq(true));
+                .processIntent(any(Intent.class), anyString(), eq(false), anyBoolean(), eq(true));
+    }
+
+    /**
+     * Ensure that if the caller was granted CALL_PHONE, but did not get the OP_CALL_PHONE app op
+     * (and is not the default dialer), a SecurityException is thrown.
+     */
+    @SmallTest
+    @Test
+    public void testPlaceCallAppOpFailure() throws Exception {
+        Uri handle = Uri.parse("tel:6505551234");
+        Bundle extras = createSampleExtras();
+
+        // The app is not considered a privileged dialer and does not have the OP_CALL_PHONE
+        // app op.
+        doNothing().when(mContext).enforceCallingOrSelfPermission(eq(CALL_PHONE), anyString());
+        when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(),
+                nullable(String.class), nullable(String.class)))
+                .thenReturn(AppOpsManager.MODE_IGNORED);
+
+        try {
+            mTSIBinder.placeCall(handle, extras, "arbitrary_package_name", null);
+            fail("Expected SecurityException because CALL_PHONE was not granted to caller");
+        } catch (SecurityException e) {
+            // expected
+        }
+
+        verify(mUserCallIntentProcessor, never())
+                .processIntent(any(Intent.class), anyString(), eq(false), anyBoolean(), eq(true));
     }
 
     @SmallTest
@@ -1090,6 +1859,29 @@
 
     @SmallTest
     @Test
+    public void testGetDefaultDialerPackageForUser() throws Exception {
+        final int userId = 1;
+        final String packageName = "some.package";
+
+        when(mDefaultDialerCache.getDefaultDialerApplication(userId))
+                .thenReturn(packageName);
+
+        assertEquals(packageName, mTSIBinder.getDefaultDialerPackageForUser(userId));
+    }
+
+    @SmallTest
+    @Test
+    public void testGetSystemDialerPackage() throws Exception {
+        final String packageName = "some.package";
+
+        when(mDefaultDialerCache.getSystemDialerApplication())
+                .thenReturn(packageName);
+
+        assertEquals(packageName, mTSIBinder.getSystemDialerPackage(CALLING_PACKAGE));
+    }
+
+    @SmallTest
+    @Test
     public void testEndCallWithRingingForegroundCall() throws Exception {
         Call call = mock(Call.class);
         when(call.getState()).thenReturn(CallState.RINGING);
@@ -1123,8 +1915,7 @@
     public void testEndCallWithNoForegroundCall() throws Exception {
         Call call = mock(Call.class);
         when(call.getState()).thenReturn(CallState.ACTIVE);
-        when(mFakeCallsManager.getFirstCallWithState(any()))
-                .thenReturn(call);
+        when(mFakeCallsManager.getFirstCallWithState(any())).thenReturn(call);
         assertTrue(mTSIBinder.endCall(TEST_PACKAGE));
         verify(mFakeCallsManager).disconnectCall(eq(call));
     }
@@ -1165,14 +1956,16 @@
     @SmallTest
     @Test
     public void testIsInCall() throws Exception {
-        when(mFakeCallsManager.hasOngoingCalls()).thenReturn(true);
+        when(mFakeCallsManager.hasOngoingCalls(any(UserHandle.class), anyBoolean()))
+                .thenReturn(true);
         assertTrue(mTSIBinder.isInCall(DEFAULT_DIALER_PACKAGE, null));
     }
 
     @SmallTest
     @Test
     public void testNotIsInCall() throws Exception {
-        when(mFakeCallsManager.hasOngoingCalls()).thenReturn(false);
+        when(mFakeCallsManager.hasOngoingCalls(any(UserHandle.class), anyBoolean()))
+                .thenReturn(false);
         assertFalse(mTSIBinder.isInCall(DEFAULT_DIALER_PACKAGE, null));
     }
 
@@ -1187,20 +1980,22 @@
         } catch (SecurityException e) {
             // desired result
         }
-        verify(mFakeCallsManager, never()).hasOngoingCalls();
+        verify(mFakeCallsManager, never()).hasOngoingCalls(any(UserHandle.class), anyBoolean());
     }
 
     @SmallTest
     @Test
     public void testIsInManagedCall() throws Exception {
-        when(mFakeCallsManager.hasOngoingManagedCalls()).thenReturn(true);
+        when(mFakeCallsManager.hasOngoingManagedCalls(any(UserHandle.class), anyBoolean()))
+                .thenReturn(true);
         assertTrue(mTSIBinder.isInManagedCall(DEFAULT_DIALER_PACKAGE, null));
     }
 
     @SmallTest
     @Test
     public void testNotIsInManagedCall() throws Exception {
-        when(mFakeCallsManager.hasOngoingManagedCalls()).thenReturn(false);
+        when(mFakeCallsManager.hasOngoingManagedCalls(any(UserHandle.class), anyBoolean()))
+                .thenReturn(false);
         assertFalse(mTSIBinder.isInManagedCall(DEFAULT_DIALER_PACKAGE, null));
     }
 
@@ -1215,7 +2010,7 @@
         } catch (SecurityException e) {
             // desired result
         }
-        verify(mFakeCallsManager, never()).hasOngoingCalls();
+        verify(mFakeCallsManager, never()).hasOngoingCalls(any(UserHandle.class), anyBoolean());
     }
 
     /**
@@ -1251,6 +2046,22 @@
         verify(mFakeCallsManager, never()).answerCall(eq(call), anyInt());
     }
 
+    @SmallTest
+    @Test
+    public void testGetAdnUriForPhoneAccount() throws Exception {
+        final int subId = 1;
+        final Uri adnUri = Uri.parse("content://icc/adn/subId/" + subId);
+        PhoneAccount phoneAccount = makePhoneAccount(TEL_PA_HANDLE_CURRENT).build();
+        when(mFakePhoneAccountRegistrar.getPhoneAccount(
+                eq(TEL_PA_HANDLE_CURRENT), any(UserHandle.class)))
+                .thenReturn(phoneAccount);
+        when(mFakePhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(TEL_PA_HANDLE_CURRENT))
+                .thenReturn(subId);
+
+        assertEquals(adnUri,
+                mTSIBinder.getAdnUriForPhoneAccount(TEL_PA_HANDLE_CURRENT, DEFAULT_DIALER_PACKAGE));
+    }
+
     /**
      * Register phone accounts for the supplied PhoneAccountHandles to make them
      * visible to all users (via the isVisibleToCaller method in TelecomServiceImpl.
@@ -1275,6 +2086,12 @@
         return paBuilder;
     }
 
+    private PhoneAccount.Builder makeSelfManagedPhoneAccount(PhoneAccountHandle paHandle) {
+        PhoneAccount.Builder paBuilder = makePhoneAccount(paHandle);
+        paBuilder.setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED);
+        return paBuilder;
+    }
+
     private PhoneAccount.Builder makePhoneAccount(PhoneAccountHandle paHandle) {
         return new PhoneAccount.Builder(paHandle, "testLabel");
     }
@@ -1286,6 +2103,8 @@
     }
 
     private static boolean areBundlesEqual(Bundle b1, Bundle b2) {
+        if (b1.keySet().size() != b2.keySet().size()) return false;
+
         for (String key1 : b1.keySet()) {
             if (!b1.get(key1).equals(b2.get(key1))) {
                 return false;
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index d6ff196..d013fae 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -66,7 +66,6 @@
 import android.telecom.VideoProfile;
 import android.telephony.TelephonyManager;
 import android.telephony.TelephonyRegistryManager;
-import android.text.TextUtils;
 
 import com.android.internal.telecom.IInCallAdapter;
 import com.android.server.telecom.AsyncRingtonePlayer;
@@ -89,6 +88,7 @@
 import com.android.server.telecom.PhoneNumberUtilsAdapterImpl;
 import com.android.server.telecom.ProximitySensorManager;
 import com.android.server.telecom.ProximitySensorManagerFactory;
+import com.android.server.telecom.Ringer;
 import com.android.server.telecom.RoleManagerAdapter;
 import com.android.server.telecom.StatusBarNotifier;
 import com.android.server.telecom.SystemStateHelper;
@@ -96,6 +96,7 @@
 import com.android.server.telecom.Timeouts;
 import com.android.server.telecom.WiredHeadsetManager;
 import com.android.server.telecom.bluetooth.BluetoothRouteManager;
+import com.android.server.telecom.callfiltering.BlockedNumbersAdapter;
 import com.android.server.telecom.components.UserCallIntentProcessor;
 import com.android.server.telecom.ui.IncomingCallNotifier;
 
@@ -112,6 +113,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -119,6 +121,7 @@
  */
 public class TelecomSystemTest extends TelecomTestCase {
 
+    private static final String CALLING_PACKAGE = TelecomSystemTest.class.getPackageName();
     static final int TEST_POLL_INTERVAL = 10;  // milliseconds
     static final int TEST_TIMEOUT = 1000;  // milliseconds
 
@@ -208,6 +211,10 @@
     @Mock ToneGenerator mToneGenerator;
     @Mock DeviceIdleControllerAdapter mDeviceIdleControllerAdapter;
 
+    @Mock Ringer.AccessibilityManagerAdapter mAccessibilityManagerAdapter;
+    @Mock
+    BlockedNumbersAdapter mBlockedNumbersAdapter;
+
     final ComponentName mInCallServiceComponentNameX =
             new ComponentName(
                     "incall-service-package-X",
@@ -379,13 +386,16 @@
 
     @Override
     public void tearDown() throws Exception {
-        mTelecomSystem.getCallsManager().waitOnHandlers();
-        LinkedList<HandlerThread> handlerThreads = mTelecomSystem.getCallsManager()
-                .getGraphHandlerThreads();
-        for (HandlerThread handlerThread : handlerThreads) {
-            handlerThread.quitSafely();
+        if (mTelecomSystem != null && mTelecomSystem.getCallsManager() != null) {
+            mTelecomSystem.getCallsManager().waitOnHandlers();
+            LinkedList<HandlerThread> handlerThreads = mTelecomSystem.getCallsManager()
+                    .getGraphHandlerThreads();
+            for (HandlerThread handlerThread : handlerThreads) {
+                handlerThread.quitSafely();
+            }
+            handlerThreads.clear();
+            mTelecomSystem.getCallsManager().getVoipCallMonitor().stopMonitor();
         }
-        handlerThreads.clear();
         waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
         waitForHandlerAction(mHandlerThread.getThreadHandler(), TEST_TIMEOUT);
         // Bring down the threads that are active.
@@ -396,12 +406,19 @@
             // don't do anything
         }
 
-        mConnectionServiceFocusManager.getHandler().removeCallbacksAndMessages(null);
-        waitForHandlerAction(mConnectionServiceFocusManager.getHandler(), TEST_TIMEOUT);
-        mConnectionServiceFocusManager.getHandler().getLooper().quit();
+        if (mConnectionServiceFocusManager != null) {
+            mConnectionServiceFocusManager.getHandler().removeCallbacksAndMessages(null);
+            waitForHandlerAction(mConnectionServiceFocusManager.getHandler(), TEST_TIMEOUT);
+            mConnectionServiceFocusManager.getHandler().getLooper().quit();
+        }
 
-        mConnectionServiceFixtureA.waitForHandlerToClear();
-        mConnectionServiceFixtureB.waitForHandlerToClear();
+        if (mConnectionServiceFixtureA != null) {
+            mConnectionServiceFixtureA.waitForHandlerToClear();
+        }
+
+        if (mConnectionServiceFixtureA != null) {
+            mConnectionServiceFixtureB.waitForHandlerToClear();
+        }
 
         // Forcefully clean all sessions at the end of the test, which will also log any stale
         // sessions for debugging.
@@ -473,7 +490,8 @@
         when(mClockProxy.currentTimeMillis()).thenReturn(TEST_CREATE_TIME);
         when(mClockProxy.elapsedRealtime()).thenReturn(TEST_CREATE_ELAPSED_TIME);
         when(mRoleManagerAdapter.getCallCompanionApps()).thenReturn(Collections.emptyList());
-        when(mRoleManagerAdapter.getDefaultCallScreeningApp()).thenReturn(null);
+        when(mRoleManagerAdapter.getDefaultCallScreeningApp(any(UserHandle.class)))
+                .thenReturn(null);
         mTelecomSystem = new TelecomSystem(
                 mComponentContextFixture.getTestDouble(),
                 (context, phoneAccountRegistrar, defaultDialerCache, mDeviceIdleControllerAdapter)
@@ -498,7 +516,8 @@
                             WiredHeadsetManager wiredHeadsetManager,
                             StatusBarNotifier statusBarNotifier,
                             CallAudioManager.AudioServiceFactory audioServiceFactory,
-                            int earpieceControl) {
+                            int earpieceControl,
+                            Executor asyncTaskExecutor) {
                         return new CallAudioRouteStateMachine(context,
                                 callsManager,
                                 bluetoothManager,
@@ -507,7 +526,8 @@
                                 audioServiceFactory,
                                 // Force enable an earpiece for the end-to-end tests
                                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                                mHandlerThread.getLooper());
+                                mHandlerThread.getLooper(),
+                                Runnable::run /* async tasks as now sync for testing! */);
                     }
                 },
                 new CallAudioModeStateMachine.Factory() {
@@ -526,7 +546,9 @@
                             ContactsAsyncHelper.ContentResolverAdapter adapter) {
                         return new ContactsAsyncHelper(adapter, mHandlerThread.getLooper());
                     }
-                }, mDeviceIdleControllerAdapter);
+                }, mDeviceIdleControllerAdapter, mAccessibilityManagerAdapter,
+                Runnable::run,
+                mBlockedNumbersAdapter);
 
         mComponentContextFixture.setTelecomManager(new TelecomManager(
                 mComponentContextFixture.getTestDouble(),
@@ -743,7 +765,7 @@
         final UserHandle userHandle = initiatingUser;
         Context localAppContext = mComponentContextFixture.getTestDouble().getApplicationContext();
         new UserCallIntentProcessor(localAppContext, userHandle).processIntent(
-                actionCallIntent, null, true /* hasCallAppOp*/, false /* isLocal */);
+                actionCallIntent, null, false, true /* hasCallAppOp*/, false /* isLocal */);
         // Wait for handler to start CallerInfo lookup.
         waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
         // Send the CallerInfo lookup reply.
@@ -904,7 +926,7 @@
                 TelecomManager.EXTRA_INCOMING_CALL_ADDRESS,
                 Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null));
         mTelecomSystem.getTelecomServiceImpl().getBinder()
-                .addNewIncomingCall(phoneAccountHandle, extras);
+                .addNewIncomingCall(phoneAccountHandle, extras, CALLING_PACKAGE);
 
         verify(connectionServiceFixture.getTestDouble())
                 .createConnection(any(PhoneAccountHandle.class), anyString(),
diff --git a/tests/src/com/android/server/telecom/tests/TestScheduledExecutorService.java b/tests/src/com/android/server/telecom/tests/TestScheduledExecutorService.java
new file mode 100644
index 0000000..22a0be1
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/TestScheduledExecutorService.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2022 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.server.telecom.tests;
+
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.Callable;
+import java.util.concurrent.Delayed;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * A test implementation of a scheduled executor service.
+ */
+public class TestScheduledExecutorService implements ScheduledExecutorService {
+    private static final String TAG = "TestScheduledExecutorService";
+
+    private class CompletedFuture<T> implements Future<T>, ScheduledFuture<T> {
+
+        private final Callable<T> mTask;
+        private final long mDelayMs;
+        private Runnable mRunnable;
+
+        CompletedFuture(Callable<T> task) {
+            mTask = task;
+            mDelayMs = 0;
+        }
+
+        @SuppressWarnings("unused")
+        CompletedFuture(Callable<T> task, long delayMs) {
+            mTask = task;
+            mDelayMs = delayMs;
+        }
+
+        CompletedFuture(Runnable task, long delayMs) {
+            mRunnable = task;
+            mTask = (Callable<T>) Executors.callable(task);
+            mDelayMs = delayMs;
+        }
+
+        @Override
+        public boolean cancel(boolean mayInterruptIfRunning) {
+            cancelRunnable(mRunnable);
+            return true;
+        }
+
+        @Override
+        public boolean isCancelled() {
+            return false;
+        }
+
+        @Override
+        public boolean isDone() {
+            return true;
+        }
+
+        @Override
+        public T get() throws InterruptedException, ExecutionException {
+            try {
+                return mTask.call();
+            } catch (Exception e) {
+                throw new ExecutionException(e);
+            }
+        }
+
+        @Override
+        public T get(long timeout, TimeUnit unit)
+                throws InterruptedException, ExecutionException, TimeoutException {
+            try {
+                return mTask.call();
+            } catch (Exception e) {
+                throw new ExecutionException(e);
+            }
+        }
+
+        @Override
+        public long getDelay(TimeUnit unit) {
+            if (unit == TimeUnit.MILLISECONDS) {
+                return mDelayMs;
+            } else {
+                // not implemented
+                return 0;
+            }
+        }
+
+        @Override
+        public int compareTo(Delayed o) {
+            if (o == null) return 1;
+            if (o.getDelay(TimeUnit.MILLISECONDS) > mDelayMs) return -1;
+            if (o.getDelay(TimeUnit.MILLISECONDS) < mDelayMs) return 1;
+            return 0;
+        }
+    }
+
+    private long mClock = 0;
+    private Map<Long, Runnable> mScheduledRunnables = new HashMap<>();
+    private Map<Runnable, Long> mRepeatDuration = new HashMap<>();
+
+    @Override
+    public void shutdown() {
+    }
+
+    @Override
+    public List<Runnable> shutdownNow() {
+        return null;
+    }
+
+    @Override
+    public boolean isShutdown() {
+        return false;
+    }
+
+    @Override
+    public boolean isTerminated() {
+        return false;
+    }
+
+    @Override
+    public boolean awaitTermination(long timeout, TimeUnit unit) {
+        return false;
+    }
+
+    @Override
+    public <T> Future<T> submit(Callable<T> task) {
+        return new TestScheduledExecutorService.CompletedFuture<>(task);
+    }
+
+    @Override
+    public <T> Future<T> submit(Runnable task, T result) {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    @Override
+    public Future<?> submit(Runnable task) {
+        task.run();
+        return new TestScheduledExecutorService.CompletedFuture<>(() -> null);
+    }
+
+    @Override
+    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    @Override
+    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout,
+            TimeUnit unit) {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    @Override
+    public <T> T invokeAny(Collection<? extends Callable<T>> tasks) {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    @Override
+    public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    @Override
+    public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
+        // Schedule the runnable for execution at the specified time.
+        long scheduledTime = getNextExecutionTime(delay, unit);
+        mScheduledRunnables.put(scheduledTime, command);
+
+        Log.i(TAG, "schedule: runnable=" + System.identityHashCode(command) + ", time="
+                + scheduledTime);
+
+        return new TestScheduledExecutorService.CompletedFuture<Runnable>(command, delay);
+    }
+
+    @Override
+    public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
+        throw new UnsupportedOperationException("Not implemented");
+    }
+
+    @Override
+    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period,
+            TimeUnit unit) {
+        return scheduleWithFixedDelay(command, initialDelay, period, unit);
+    }
+
+    @Override
+    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay,
+            long delay, TimeUnit unit) {
+        // Schedule the runnable for execution at the specified time.
+        long nextScheduledTime = getNextExecutionTime(delay, unit);
+        mScheduledRunnables.put(nextScheduledTime, command);
+        mRepeatDuration.put(command, unit.toMillis(delay));
+
+        return new TestScheduledExecutorService.CompletedFuture<Runnable>(command, delay);
+    }
+
+    private long getNextExecutionTime(long delay, TimeUnit unit) {
+        long delayMillis = unit.toMillis(delay);
+        return mClock + delayMillis;
+    }
+
+    @Override
+    public void execute(Runnable command) {
+        command.run();
+    }
+
+    /**
+     * Used in unit tests, used to add a delta to the "clock" so that we can fire off scheduled
+     * items and reschedule the repeats.
+     * @param duration The duration (millis) to add to the clock.
+     */
+    public void advanceTime(long duration) {
+        Map<Long, Runnable> nextRepeats = new HashMap<>();
+        List<Runnable> toRun = new ArrayList<>();
+        mClock += duration;
+        Iterator<Map.Entry<Long, Runnable>> iterator = mScheduledRunnables.entrySet().iterator();
+        while (iterator.hasNext()) {
+            Map.Entry<Long, Runnable> entry = iterator.next();
+            if (mClock >= entry.getKey()) {
+                toRun.add(entry.getValue());
+
+                Runnable r = entry.getValue();
+                Log.i(TAG, "advanceTime: runningRunnable=" + System.identityHashCode(r));
+                // If this is a repeating scheduled item, schedule the repeat.
+                if (mRepeatDuration.containsKey(r)) {
+                    // schedule next execution
+                    nextRepeats.put(mClock + mRepeatDuration.get(r), entry.getValue());
+                }
+                iterator.remove();
+            }
+        }
+
+        // Update things at the end to avoid concurrent access.
+        mScheduledRunnables.putAll(nextRepeats);
+        toRun.forEach(r -> r.run());
+    }
+
+    /**
+     * Used from a {@link CompletedFuture} as defined above to cancel a scheduled task.
+     * @param r The runnable to cancel.
+     */
+    private void cancelRunnable(Runnable r) {
+        Optional<Map.Entry<Long, Runnable>> found = mScheduledRunnables.entrySet().stream()
+                .filter(e -> e.getValue() == r)
+                .findFirst();
+        if (found.isPresent()) {
+            mScheduledRunnables.remove(found.get().getKey());
+        }
+        mRepeatDuration.remove(r);
+        Log.i(TAG, "cancelRunnable: runnable=" + System.identityHashCode(r));
+    }
+
+    public int getNumberOfScheduledRunnables() {
+        return mScheduledRunnables.size();
+    }
+
+    public boolean isRunnableScheduledAtTime(long time) {
+        return mScheduledRunnables.containsKey(time);
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/TransactionTests.java b/tests/src/com/android/server/telecom/tests/TransactionTests.java
new file mode 100644
index 0000000..1e6734b
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/TransactionTests.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2022 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.server.telecom.tests;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.isA;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.OutcomeReceiver;
+import android.os.UserHandle;
+import android.telecom.CallAttributes;
+import android.telecom.DisconnectCause;
+import android.telecom.PhoneAccountHandle;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.CallerInfoLookupHelper;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.ClockProxy;
+import com.android.server.telecom.PhoneNumberUtilsAdapter;
+import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.ui.ToastFactory;
+import com.android.server.telecom.voip.AnswerCallTransaction;
+import com.android.server.telecom.voip.EndCallTransaction;
+import com.android.server.telecom.voip.HoldCallTransaction;
+import com.android.server.telecom.voip.IncomingCallTransaction;
+import com.android.server.telecom.voip.OutgoingCallTransaction;
+import com.android.server.telecom.voip.HoldActiveCallForNewCallTransaction;
+import com.android.server.telecom.voip.RequestFocusTransaction;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+
+public class TransactionTests extends TelecomTestCase {
+
+    private static final String CALL_ID_1 = "1";
+
+    private static final PhoneAccountHandle mHandle = new PhoneAccountHandle(
+            new ComponentName("foo", "bar"), "1");
+    private static final String TEST_NAME = "Sergey Brin";
+    private static final Uri TEST_URI = Uri.fromParts("tel", "abc", "123");
+
+    @Mock private Call mMockCall1;
+    @Mock private Context mMockContext;
+    @Mock private CallsManager mCallsManager;
+    @Mock private ToastFactory mToastFactory;
+    @Mock private ClockProxy mClockProxy;
+    @Mock private PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter;
+    @Mock private CallerInfoLookupHelper mCallerInfoLookupHelper;
+
+    private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() {
+    };
+    private static final Uri TEST_ADDRESS = Uri.parse("tel:555-1212");
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        MockitoAnnotations.initMocks(this);
+        Mockito.when(mMockCall1.getId()).thenReturn(CALL_ID_1);
+    }
+
+    @Override
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Test
+    public void testEndCallTransactionWithDisconnect() throws Exception {
+        // GIVEN
+        EndCallTransaction transaction =
+                new EndCallTransaction(mCallsManager,  new DisconnectCause(0), mMockCall1);
+
+        // WHEN
+        transaction.processTransaction(null);
+
+        // THEN
+        verify(mCallsManager, times(1))
+                .markCallAsDisconnected(mMockCall1, new DisconnectCause(0));
+        verify(mCallsManager, never())
+                .rejectCall(mMockCall1, 0);
+        verify(mCallsManager, times(1))
+                .markCallAsRemoved(mMockCall1);
+    }
+
+    @Test
+    public void testHoldCallTransaction() throws Exception {
+        // GIVEN
+        Call spyCall = createSpyCall(null, CallState.ACTIVE, CALL_ID_1);
+
+        HoldCallTransaction transaction =
+                new HoldCallTransaction(mCallsManager, spyCall);
+
+        // WHEN
+        when(mCallsManager.canHold(spyCall)).thenReturn(true);
+        doAnswer(invocation -> {
+            Call call = invocation.getArgument(0);
+            call.setState(CallState.ON_HOLD, "manual set");
+            return null;
+        }).when(mCallsManager).markCallAsOnHold(spyCall);
+
+        transaction.processTransaction(null);
+
+        // THEN
+        verify(mCallsManager, times(1))
+                .markCallAsOnHold(spyCall);
+
+        assertEquals(CallState.ON_HOLD, spyCall.getState());
+    }
+
+    @Test
+    public void testTransactionalRequestFocus() throws Exception {
+        // GIVEN
+        RequestFocusTransaction transaction =
+                new RequestFocusTransaction(mCallsManager, mMockCall1);
+
+        // WHEN
+        transaction.processTransaction(null);
+
+        // THEN
+        verify(mCallsManager, times(1))
+                .transactionRequestNewFocusCall(eq(mMockCall1), eq(CallState.ACTIVE),
+                        isA(OutcomeReceiver.class));
+    }
+
+    @Test
+    public void testAnswerCallTransaction() throws Exception {
+        // GIVEN
+        AnswerCallTransaction transaction =
+                new AnswerCallTransaction(mCallsManager, mMockCall1, 0);
+
+        // WHEN
+        transaction.processTransaction(null);
+
+        // THEN
+        verify(mCallsManager, times(1))
+                .transactionRequestNewFocusCall(eq(mMockCall1), eq(CallState.ANSWERED),
+                        isA(OutcomeReceiver.class));
+    }
+
+    @Test
+    public void testTransactionalHoldActiveCallForNewCall() throws Exception {
+        // GIVEN
+        HoldActiveCallForNewCallTransaction transaction =
+                new HoldActiveCallForNewCallTransaction(mCallsManager, mMockCall1);
+
+        // WHEN
+        transaction.processTransaction(null);
+
+        // THEN
+        verify(mCallsManager, times(1))
+                .transactionHoldPotentialActiveCallForNewCall(eq(mMockCall1),
+                        isA(OutcomeReceiver.class));
+    }
+
+    @Test
+    public void testOutgoingCallTransaction() throws Exception {
+        // GIVEN
+        CallAttributes callAttributes = new CallAttributes.Builder(mHandle,
+                CallAttributes.DIRECTION_OUTGOING, TEST_NAME, TEST_URI).build();
+
+        OutgoingCallTransaction transaction =
+                new OutgoingCallTransaction(CALL_ID_1, mMockContext, callAttributes, mCallsManager);
+
+        // WHEN
+        when(mMockContext.getOpPackageName()).thenReturn("testPackage");
+        when(mMockContext.checkCallingPermission(android.Manifest.permission.CALL_PRIVILEGED))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+        when(mCallsManager.isOutgoingCallPermitted(callAttributes.getPhoneAccountHandle()))
+                .thenReturn(true);
+        transaction.processTransaction(null);
+
+        // THEN
+        verify(mCallsManager, times(1))
+                .startOutgoingCall(isA(Uri.class),
+                        isA(PhoneAccountHandle.class),
+                        isA(Bundle.class),
+                        isA(UserHandle.class),
+                        isA(Intent.class),
+                        nullable(String.class));
+    }
+
+    @Test
+    public void testIncomingCallTransaction() throws Exception {
+        // GIVEN
+        CallAttributes callAttributes = new CallAttributes.Builder(mHandle,
+                CallAttributes.DIRECTION_INCOMING, TEST_NAME, TEST_URI).build();
+
+        IncomingCallTransaction transaction =
+                new IncomingCallTransaction(CALL_ID_1, callAttributes, mCallsManager);
+
+        // WHEN
+        when(mCallsManager.isIncomingCallPermitted(callAttributes.getPhoneAccountHandle()))
+                .thenReturn(true);
+        transaction.processTransaction(null);
+
+        // THEN
+        verify(mCallsManager, times(1))
+                .processIncomingCallIntent(isA(PhoneAccountHandle.class),
+                        isA(Bundle.class),
+                        isA(Boolean.class));
+    }
+
+    private Call createSpyCall(PhoneAccountHandle targetPhoneAccount, int initialState, String id) {
+        when(mCallsManager.getCallerInfoLookupHelper()).thenReturn(mCallerInfoLookupHelper);
+
+        Call call = new Call(id,
+                mMockContext,
+                mCallsManager,
+                mLock, /* ConnectionServiceRepository */
+                null,
+                mPhoneNumberUtilsAdapter,
+                TEST_ADDRESS,
+                null /* GatewayInfo */,
+                null /* ConnectionManagerAccount */,
+                targetPhoneAccount,
+                Call.CALL_DIRECTION_INCOMING,
+                false /* shouldAttachToExistingConnection*/,
+                false /* isConference */,
+                mClockProxy,
+                mToastFactory);
+
+        Call callSpy = Mockito.spy(call);
+
+        callSpy.setState(initialState, "manual set in test");
+
+        // Mocks some methods to not call the real method.
+        doNothing().when(callSpy).unhold();
+        doNothing().when(callSpy).hold();
+        doNothing().when(callSpy).disconnect();
+
+        return callSpy;
+    }
+}
\ No newline at end of file
diff --git a/tests/src/com/android/server/telecom/tests/TransactionalServiceWrapperTest.java b/tests/src/com/android/server/telecom/tests/TransactionalServiceWrapperTest.java
new file mode 100644
index 0000000..98624d4
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/TransactionalServiceWrapperTest.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2022 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.server.telecom.tests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.isA;
+
+
+import android.content.ComponentName;
+import android.os.OutcomeReceiver;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.telecom.DisconnectCause;
+import android.telecom.PhoneAccountHandle;
+
+import com.android.internal.telecom.ICallControl;
+import com.android.internal.telecom.ICallEventCallback;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.TransactionalServiceRepository;
+import com.android.server.telecom.TransactionalServiceWrapper;
+import com.android.server.telecom.voip.EndCallTransaction;
+import com.android.server.telecom.voip.HoldCallTransaction;
+import com.android.server.telecom.voip.SerialTransaction;
+import com.android.server.telecom.voip.TransactionManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(JUnit4.class)
+public class TransactionalServiceWrapperTest extends TelecomTestCase {
+
+    private static final PhoneAccountHandle SERVICE_HANDLE = new PhoneAccountHandle(
+            ComponentName.unflattenFromString("com.foo/.Blah"), "Service1");
+
+    private static final String CALL_ID_1 = "1";
+    private static final String CALL_ID_2 = "2";
+
+    TransactionalServiceWrapper mTransactionalServiceWrapper;
+
+    @Mock private Call mMockCall1;
+    @Mock private Call mMockCall2;
+    @Mock private CallsManager mCallsManager;
+    @Mock private TransactionManager mTransactionManager;
+    @Mock private ICallEventCallback mCallEventCallback;
+    @Mock private TransactionalServiceRepository mRepository;
+    private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() {};
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        MockitoAnnotations.initMocks(this);
+        Mockito.when(mMockCall1.getId()).thenReturn(CALL_ID_1);
+        Mockito.when(mMockCall2.getId()).thenReturn(CALL_ID_2);
+        Mockito.when(mCallsManager.getLock()).thenReturn(mLock);
+
+        mTransactionalServiceWrapper = new TransactionalServiceWrapper(mCallEventCallback,
+                mCallsManager, SERVICE_HANDLE, mMockCall1, mRepository);
+
+        mTransactionalServiceWrapper.setTransactionManager(mTransactionManager);
+    }
+
+    @Override
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @Test
+    public void testTransactionalServiceWrapperStartState() throws Exception {
+        TransactionalServiceWrapper service =
+                new TransactionalServiceWrapper(mCallEventCallback,
+                        mCallsManager, SERVICE_HANDLE, mMockCall1, mRepository);
+
+        assertEquals(SERVICE_HANDLE, service.getPhoneAccountHandle());
+        assertEquals(1, service.getNumberOfTrackedCalls());
+    }
+
+    @Test
+    public void testTransactionalServiceWrapperCallCount() throws Exception {
+        TransactionalServiceWrapper service =
+                new TransactionalServiceWrapper(mCallEventCallback,
+                        mCallsManager, SERVICE_HANDLE, mMockCall1, mRepository);
+
+        assertEquals(1, service.getNumberOfTrackedCalls());
+        service.trackCall(mMockCall2);
+        assertEquals(2, service.getNumberOfTrackedCalls());
+
+        assertTrue(service.untrackCall(mMockCall2));
+        assertEquals(1, service.getNumberOfTrackedCalls());
+
+        assertTrue(service.untrackCall(mMockCall1));
+        assertFalse(service.untrackCall(mMockCall1));
+        assertEquals(0, service.getNumberOfTrackedCalls());
+    }
+
+    @Test
+    public void testCallControlSetActive() throws RemoteException {
+        // GIVEN
+        mTransactionalServiceWrapper.trackCall(mMockCall1);
+
+        // WHEN
+        ICallControl callControl = mTransactionalServiceWrapper.getICallControl();
+        callControl.setActive(CALL_ID_1, new ResultReceiver(null));
+
+        //THEN
+        verify(mTransactionManager, times(1))
+                .addTransaction(isA(SerialTransaction.class), isA(OutcomeReceiver.class));
+    }
+
+    @Test
+    public void testCallControlRejectCall() throws RemoteException {
+        // GIVEN
+        mTransactionalServiceWrapper.trackCall(mMockCall1);
+
+        // WHEN
+        ICallControl callControl = mTransactionalServiceWrapper.getICallControl();
+        callControl.disconnect(CALL_ID_1, new DisconnectCause(DisconnectCause.REJECTED),
+                new ResultReceiver(null));
+
+        //THEN
+        verify(mTransactionManager, times(1))
+                .addTransaction(isA(EndCallTransaction.class), isA(OutcomeReceiver.class));
+    }
+
+    @Test
+    public void testCallControlDisconnectCall() throws RemoteException {
+        // GIVEN
+        mTransactionalServiceWrapper.trackCall(mMockCall1);
+
+        // WHEN
+        ICallControl callControl = mTransactionalServiceWrapper.getICallControl();
+        callControl.disconnect(CALL_ID_1, new DisconnectCause(DisconnectCause.LOCAL),
+                new ResultReceiver(null));
+
+        //THEN
+        verify(mTransactionManager, times(1))
+                .addTransaction(isA(EndCallTransaction.class), isA(OutcomeReceiver.class));
+    }
+
+    @Test
+    public void testCallControlSetInactive() throws RemoteException {
+        // GIVEN
+        mTransactionalServiceWrapper.trackCall(mMockCall1);
+
+        // WHEN
+        ICallControl callControl = mTransactionalServiceWrapper.getICallControl();
+        callControl.setInactive(CALL_ID_1, new ResultReceiver(null));
+
+        //THEN
+        verify(mTransactionManager, times(1))
+                .addTransaction(isA(HoldCallTransaction.class), isA(OutcomeReceiver.class));
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/VoipCallMonitorTest.java b/tests/src/com/android/server/telecom/tests/VoipCallMonitorTest.java
new file mode 100644
index 0000000..346b3d8
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/VoipCallMonitorTest.java
@@ -0,0 +1,218 @@
+/*
+ * Copyright (C) 2022 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.server.telecom.tests;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManagerInternal;
+import android.app.ForegroundServiceDelegationOptions;
+import android.content.ComponentName;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.service.notification.NotificationListenerService;
+import android.telecom.PhoneAccountHandle;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.voip.VoipCallMonitor;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+@RunWith(JUnit4.class)
+public class VoipCallMonitorTest extends TelecomTestCase {
+    private VoipCallMonitor mMonitor;
+    private static final String PKG_NAME_1 = "telecom.voip.test1";
+    private static final String PKG_NAME_2 = "telecom.voip.test2";
+    private static final String CLS_NAME = "VoipActivity";
+    private static final String ID_1 = "id1";
+    private static final UserHandle USER_HANDLE_1 = new UserHandle(1);
+    private static final long TIMEOUT = 5000L;
+
+    @Mock private TelecomSystem.SyncRoot mLock;
+    @Mock private ActivityManagerInternal mActivityManagerInternal;
+    @Mock private NotificationListenerService mListenerService;
+
+    private final PhoneAccountHandle mHandle1User1 = new PhoneAccountHandle(
+            new ComponentName(PKG_NAME_1, CLS_NAME), ID_1, USER_HANDLE_1);
+    private final PhoneAccountHandle mHandle2User1 = new PhoneAccountHandle(
+            new ComponentName(PKG_NAME_2, CLS_NAME), ID_1, USER_HANDLE_1);
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        mMonitor = new VoipCallMonitor(mContext, mLock);
+        mActivityManagerInternal = mock(ActivityManagerInternal.class);
+        mListenerService = mock(NotificationListenerService.class);
+        mMonitor.setActivityManagerInternal(mActivityManagerInternal);
+        mMonitor.setNotificationListenerService(mListenerService);
+        doNothing().when(mListenerService).registerAsSystemService(eq(mContext),
+                any(ComponentName.class), anyInt());
+        mMonitor.startMonitor();
+    }
+
+    @SmallTest
+    @Test
+    public void testStartMonitorForOneCall() {
+        Call call = createTestCall("testCall", mHandle1User1);
+        IBinder service = mock(IBinder.class);
+
+        ArgumentCaptor<ServiceConnection> captor = ArgumentCaptor.forClass(ServiceConnection.class);
+        mMonitor.onCallAdded(call);
+        verify(mActivityManagerInternal, timeout(TIMEOUT)).startForegroundServiceDelegate(any(
+                ForegroundServiceDelegationOptions.class), captor.capture());
+        ServiceConnection conn = captor.getValue();
+        conn.onServiceConnected(mHandle1User1.getComponentName(), service);
+
+        mMonitor.onCallRemoved(call);
+        verify(mActivityManagerInternal, timeout(TIMEOUT)).stopForegroundServiceDelegate(eq(conn));
+    }
+
+    @SmallTest
+    @Test
+    public void testMonitorForTwoCallsOnSameHandle() {
+        Call call1 = createTestCall("testCall1", mHandle1User1);
+        Call call2 = createTestCall("testCall2", mHandle1User1);
+        IBinder service = mock(IBinder.class);
+
+        ArgumentCaptor<ServiceConnection> captor1 =
+                ArgumentCaptor.forClass(ServiceConnection.class);
+        mMonitor.onCallAdded(call1);
+        verify(mActivityManagerInternal, timeout(TIMEOUT).times(1))
+                .startForegroundServiceDelegate(any(ForegroundServiceDelegationOptions.class),
+                        captor1.capture());
+        ServiceConnection conn1 = captor1.getValue();
+        conn1.onServiceConnected(mHandle1User1.getComponentName(), service);
+
+        ArgumentCaptor<ServiceConnection> captor2 =
+                ArgumentCaptor.forClass(ServiceConnection.class);
+        mMonitor.onCallAdded(call2);
+        verify(mActivityManagerInternal, timeout(TIMEOUT).times(2))
+                .startForegroundServiceDelegate(any(ForegroundServiceDelegationOptions.class),
+                        captor2.capture());
+        ServiceConnection conn2 = captor2.getValue();
+        conn2.onServiceConnected(mHandle1User1.getComponentName(), service);
+
+        mMonitor.onCallRemoved(call1);
+        verify(mActivityManagerInternal, never()).stopForegroundServiceDelegate(
+                any(ServiceConnection.class));
+        mMonitor.onCallRemoved(call2);
+        verify(mActivityManagerInternal, timeout(TIMEOUT).times(1))
+                .stopForegroundServiceDelegate(eq(conn2));
+    }
+
+    @SmallTest
+    @Test
+    public void testMonitorForTwoCallsOnDifferentHandle() {
+        Call call1 = createTestCall("testCall1", mHandle1User1);
+        Call call2 = createTestCall("testCall2", mHandle2User1);
+        IBinder service = mock(IBinder.class);
+
+        ArgumentCaptor<ServiceConnection> connCaptor1 = ArgumentCaptor.forClass(
+                ServiceConnection.class);
+        ArgumentCaptor<ForegroundServiceDelegationOptions> optionsCaptor1 =
+                ArgumentCaptor.forClass(ForegroundServiceDelegationOptions.class);
+        mMonitor.onCallAdded(call1);
+        verify(mActivityManagerInternal, timeout(TIMEOUT).times(1))
+                .startForegroundServiceDelegate(optionsCaptor1.capture(), connCaptor1.capture());
+        ForegroundServiceDelegationOptions options1 = optionsCaptor1.getValue();
+        ServiceConnection conn1 = connCaptor1.getValue();
+        conn1.onServiceConnected(mHandle1User1.getComponentName(), service);
+        assertEquals(PKG_NAME_1, options1.getComponentName().getPackageName());
+
+        ArgumentCaptor<ServiceConnection> connCaptor2 = ArgumentCaptor.forClass(
+                ServiceConnection.class);
+        ArgumentCaptor<ForegroundServiceDelegationOptions> optionsCaptor2 =
+                ArgumentCaptor.forClass(ForegroundServiceDelegationOptions.class);
+        mMonitor.onCallAdded(call2);
+        verify(mActivityManagerInternal, timeout(TIMEOUT).times(2))
+                .startForegroundServiceDelegate(optionsCaptor2.capture(), connCaptor2.capture());
+        ForegroundServiceDelegationOptions options2 = optionsCaptor2.getValue();
+        ServiceConnection conn2 = connCaptor2.getValue();
+        conn2.onServiceConnected(mHandle2User1.getComponentName(), service);
+        assertEquals(PKG_NAME_2, options2.getComponentName().getPackageName());
+
+        mMonitor.onCallRemoved(call2);
+        verify(mActivityManagerInternal).stopForegroundServiceDelegate(eq(conn2));
+        mMonitor.onCallRemoved(call1);
+        verify(mActivityManagerInternal).stopForegroundServiceDelegate(eq(conn1));
+    }
+
+    @SmallTest
+    @Test
+    public void testStopDelegation() {
+        Call call1 = createTestCall("testCall1", mHandle1User1);
+        Call call2 = createTestCall("testCall2", mHandle1User1);
+        IBinder service = mock(IBinder.class);
+
+        ArgumentCaptor<ServiceConnection> captor1 =
+                ArgumentCaptor.forClass(ServiceConnection.class);
+        mMonitor.onCallAdded(call1);
+        verify(mActivityManagerInternal, timeout(TIMEOUT).times(1))
+                .startForegroundServiceDelegate(any(ForegroundServiceDelegationOptions.class),
+                        captor1.capture());
+        ServiceConnection conn1 = captor1.getValue();
+        conn1.onServiceConnected(mHandle1User1.getComponentName(), service);
+
+        ArgumentCaptor<ServiceConnection> captor2 =
+                ArgumentCaptor.forClass(ServiceConnection.class);
+        mMonitor.onCallAdded(call2);
+        verify(mActivityManagerInternal, timeout(TIMEOUT).times(2))
+                .startForegroundServiceDelegate(any(ForegroundServiceDelegationOptions.class),
+                        captor2.capture());
+        ServiceConnection conn2 = captor2.getValue();
+        conn2.onServiceConnected(mHandle1User1.getComponentName(), service);
+
+        mMonitor.stopFGSDelegation(mHandle1User1);
+        verify(mActivityManagerInternal, timeout(TIMEOUT).times(1))
+                .stopForegroundServiceDelegate(eq(conn2));
+        conn2.onServiceDisconnected(mHandle1User1.getComponentName());
+        mMonitor.onCallRemoved(call1);
+        verify(mActivityManagerInternal, timeout(TIMEOUT).times(1))
+                .stopForegroundServiceDelegate(any(ServiceConnection.class));
+    }
+
+    private Call createTestCall(String id, PhoneAccountHandle handle) {
+        Call call = mock(Call.class);
+        when(call.getTargetPhoneAccount()).thenReturn(handle);
+        when(call.isTransactionalCall()).thenReturn(true);
+        when(call.getExtras()).thenReturn(new Bundle());
+        when(call.getId()).thenReturn(id);
+        when(call.getCallingPackageIdentity()).thenReturn( new Call.CallingPackageIdentity() );
+        return call;
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/VoipCallTransactionTest.java b/tests/src/com/android/server/telecom/tests/VoipCallTransactionTest.java
new file mode 100644
index 0000000..62b8bc4
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/VoipCallTransactionTest.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2022 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.server.telecom.tests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.os.OutcomeReceiver;
+import android.telecom.CallException;
+import android.util.Log;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.voip.ParallelTransaction;
+import com.android.server.telecom.voip.SerialTransaction;
+import com.android.server.telecom.voip.TransactionManager;
+import com.android.server.telecom.voip.VoipCallTransaction;
+import com.android.server.telecom.voip.VoipCallTransactionResult;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+@RunWith(JUnit4.class)
+public class VoipCallTransactionTest extends TelecomTestCase {
+    private StringBuilder mLog;
+    private TransactionManager mTransactionManager;
+    private static final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
+
+    private class TestVoipCallTransaction extends VoipCallTransaction {
+        public static final int SUCCESS = 0;
+        public static final int FAILED = 1;
+        public static final int TIMEOUT = 2;
+
+        private long mSleepTime;
+        private String mName;
+        private int mType;
+
+        public TestVoipCallTransaction(String name, long sleepTime, int type) {
+            super(mLock);
+            mName = name;
+            mSleepTime = sleepTime;
+            mType = type;
+        }
+
+        @Override
+        public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+            CompletableFuture<VoipCallTransactionResult> resultFuture = new CompletableFuture<>();
+            mHandler.postDelayed(() -> {
+                if (mType == SUCCESS) {
+                    mLog.append(mName).append(" success;\n");
+                    resultFuture.complete(
+                            new VoipCallTransactionResult(VoipCallTransactionResult.RESULT_SUCCEED,
+                                    null));
+                } else if (mType == FAILED) {
+                    mLog.append(mName).append(" failed;\n");
+                    resultFuture.complete(
+                            new VoipCallTransactionResult(VoipCallTransactionResult.RESULT_FAILED,
+                                    null));
+                } else {
+                    mLog.append(mName).append(" timeout;\n");
+                    resultFuture.complete(
+                            new VoipCallTransactionResult(VoipCallTransactionResult.RESULT_FAILED,
+                                    "timeout"));
+                }
+            }, mSleepTime);
+            return resultFuture;
+        }
+    }
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        mTransactionManager = TransactionManager.getTestInstance();
+        mLog = new StringBuilder();
+    }
+
+    @Override
+    @After
+    public void tearDown() throws Exception {
+        Log.i("Grace", mLog.toString());
+        mTransactionManager.clear();
+        super.tearDown();
+    }
+
+    @SmallTest
+    @Test
+    public void testSerialTransactionSuccess()
+            throws ExecutionException, InterruptedException, TimeoutException {
+        List<VoipCallTransaction> subTransactions = new ArrayList<>();
+        VoipCallTransaction t1 = new TestVoipCallTransaction("t1", 1000L,
+                TestVoipCallTransaction.SUCCESS);
+        VoipCallTransaction t2 = new TestVoipCallTransaction("t2", 1000L,
+                TestVoipCallTransaction.SUCCESS);
+        VoipCallTransaction t3 = new TestVoipCallTransaction("t3", 1000L,
+                TestVoipCallTransaction.SUCCESS);
+        subTransactions.add(t1);
+        subTransactions.add(t2);
+        subTransactions.add(t3);
+        CompletableFuture<VoipCallTransactionResult> resultFuture = new CompletableFuture<>();
+        OutcomeReceiver<VoipCallTransactionResult, CallException> outcomeReceiver =
+                resultFuture::complete;
+        String expectedLog = "t1 success;\nt2 success;\nt3 success;\n";
+        mTransactionManager.addTransaction(new SerialTransaction(subTransactions, mLock),
+                outcomeReceiver);
+        assertEquals(VoipCallTransactionResult.RESULT_SUCCEED,
+                resultFuture.get(5000L, TimeUnit.MILLISECONDS).getResult());
+        assertEquals(expectedLog, mLog.toString());
+    }
+
+    @SmallTest
+    @Test
+    public void testSerialTransactionFailed()
+            throws ExecutionException, InterruptedException, TimeoutException {
+        List<VoipCallTransaction> subTransactions = new ArrayList<>();
+        VoipCallTransaction t1 = new TestVoipCallTransaction("t1", 1000L,
+                TestVoipCallTransaction.SUCCESS);
+        VoipCallTransaction t2 = new TestVoipCallTransaction("t2", 1000L,
+                TestVoipCallTransaction.FAILED);
+        VoipCallTransaction t3 = new TestVoipCallTransaction("t3", 1000L,
+                TestVoipCallTransaction.SUCCESS);
+        subTransactions.add(t1);
+        subTransactions.add(t2);
+        subTransactions.add(t3);
+        CompletableFuture<String> exceptionFuture = new CompletableFuture<>();
+        OutcomeReceiver<VoipCallTransactionResult, CallException> outcomeReceiver =
+                new OutcomeReceiver<VoipCallTransactionResult, CallException>() {
+                    @Override
+                    public void onResult(VoipCallTransactionResult result) {
+
+                    }
+
+                    @Override
+                    public void onError(CallException e) {
+                        exceptionFuture.complete(e.getMessage());
+                    }
+                };
+        mTransactionManager.addTransaction(new SerialTransaction(subTransactions, mLock),
+                outcomeReceiver);
+        exceptionFuture.get(5000L, TimeUnit.MILLISECONDS);
+        String expectedLog = "t1 success;\nt2 failed;\n";
+        assertEquals(expectedLog, mLog.toString());
+    }
+
+    @SmallTest
+    @Test
+    public void testParallelTransactionSuccess()
+            throws ExecutionException, InterruptedException, TimeoutException {
+        List<VoipCallTransaction> subTransactions = new ArrayList<>();
+        VoipCallTransaction t1 = new TestVoipCallTransaction("t1", 1000L,
+                TestVoipCallTransaction.SUCCESS);
+        VoipCallTransaction t2 = new TestVoipCallTransaction("t2", 500L,
+                TestVoipCallTransaction.SUCCESS);
+        VoipCallTransaction t3 = new TestVoipCallTransaction("t3", 200L,
+                TestVoipCallTransaction.SUCCESS);
+        subTransactions.add(t1);
+        subTransactions.add(t2);
+        subTransactions.add(t3);
+        CompletableFuture<VoipCallTransactionResult> resultFuture = new CompletableFuture<>();
+        OutcomeReceiver<VoipCallTransactionResult, CallException> outcomeReceiver =
+                resultFuture::complete;
+        mTransactionManager.addTransaction(new ParallelTransaction(subTransactions, mLock),
+                outcomeReceiver);
+        assertEquals(VoipCallTransactionResult.RESULT_SUCCEED,
+                resultFuture.get(5000L, TimeUnit.MILLISECONDS).getResult());
+        String log = mLog.toString();
+        assertTrue(log.contains("t1 success;\n"));
+        assertTrue(log.contains("t2 success;\n"));
+        assertTrue(log.contains("t3 success;\n"));
+    }
+
+    @SmallTest
+    @Test
+    public void testParallelTransactionFailed()
+            throws ExecutionException, InterruptedException, TimeoutException {
+        List<VoipCallTransaction> subTransactions = new ArrayList<>();
+        VoipCallTransaction t1 = new TestVoipCallTransaction("t1", 1000L,
+                TestVoipCallTransaction.SUCCESS);
+        VoipCallTransaction t2 = new TestVoipCallTransaction("t2", 500L,
+                TestVoipCallTransaction.FAILED);
+        VoipCallTransaction t3 = new TestVoipCallTransaction("t3", 200L,
+                TestVoipCallTransaction.SUCCESS);
+        subTransactions.add(t1);
+        subTransactions.add(t2);
+        subTransactions.add(t3);
+        CompletableFuture<String> exceptionFuture = new CompletableFuture<>();
+        OutcomeReceiver<VoipCallTransactionResult, CallException> outcomeReceiver =
+                new OutcomeReceiver<VoipCallTransactionResult, CallException>() {
+            @Override
+            public void onResult(VoipCallTransactionResult result) {
+
+            }
+
+            @Override
+            public void onError(CallException e) {
+                exceptionFuture.complete(e.getMessage());
+            }
+        };
+        mTransactionManager.addTransaction(new ParallelTransaction(subTransactions, mLock),
+                outcomeReceiver);
+        exceptionFuture.get(5000L, TimeUnit.MILLISECONDS);
+        assertTrue(mLog.toString().contains("t2 failed;\n"));
+    }
+
+    @SmallTest
+    @Test
+    public void testTransactionTimeout()
+            throws ExecutionException, InterruptedException, TimeoutException {
+        VoipCallTransaction t = new TestVoipCallTransaction("t", 10000L,
+                TestVoipCallTransaction.SUCCESS);
+        CompletableFuture<VoipCallTransactionResult> resultFuture = new CompletableFuture<>();
+        OutcomeReceiver<VoipCallTransactionResult, CallException> outcomeReceiver =
+                resultFuture::complete;
+        mTransactionManager.addTransaction(t, outcomeReceiver);
+        VoipCallTransactionResult result = resultFuture.get(7000L, TimeUnit.MILLISECONDS);
+        assertEquals(VoipCallTransactionResult.RESULT_FAILED, result.getResult());
+        assertTrue(result.getMessage().contains("timeout"));
+    }
+}