[automerger skipped] Import translations. DO NOT MERGE ANYWHERE am: 817e0dd3f3 -s ours

am skip reason: subject contains skip directive

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

Change-Id: I15a2f6a9e7013ab4783287d7ebeeef50aa524e9c
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 d122ff8..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>
@@ -277,7 +279,7 @@
         <activity android:name=".settings.EnableAccountPreferenceActivity"
              android:label="@string/enable_account_preference_title"
              android:configChanges="orientation|screenSize|keyboardHidden"
-             android:theme="@style/Theme.Telecom.DialerSettings"
+             android:theme="@style/Theme.Telecom.EnableAccount"
              android:process=":ui"
              android:exported="true">
             <intent-filter>
diff --git a/OWNERS b/OWNERS
index 39be2c1..97cc81f 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,6 +1,8 @@
 breadley@google.com
-hallliu@google.com
 tgunn@google.com
 xiaotonj@google.com
-shuoq@google.com
+chinmayd@google.com
+tjstuart@google.com
 rgreenwalt@google.com
+pmadapurmath@google.com
+grantmenke@google.com
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 95ba5c4..9226599 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/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-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 f1ea118..2945d28 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -30,7 +30,7 @@
     <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>
@@ -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 2e2f05f..5f857c1 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/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-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 d2a1f4c..ab8f454 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -30,7 +30,7 @@
     <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>
@@ -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 15928fb..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>
@@ -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 f511fcd..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>
@@ -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 50956bf..73b85d9 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/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-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 1def59a..263433d 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/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-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-night/styles.xml b/res/values-night/styles.xml
new file mode 100644
index 0000000..5b81fac
--- /dev/null
+++ b/res/values-night/styles.xml
@@ -0,0 +1,36 @@
+<?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>
+
+    <style name="Theme.Telecom.DialerSettings" parent="@android:style/Theme.DeviceDefault.Light">
+        <item name="android:forceDarkAllowed">true</item>
+        <item name="android:actionBarStyle">@style/TelecomDialerSettingsActionBarStyle</item>
+        <item name="android:actionOverflowButtonStyle">@style/TelecomDialerSettingsActionOverflowButtonStyle</item>
+        <item name="android:windowLightNavigationBar">true</item>
+        <item name="android:windowContentOverlay">@null</item>
+    </style>
+
+    <style name="Theme.Telecom.BlockedNumbers" parent="@android:style/Theme.DeviceDefault.Light">
+        <item name="android:forceDarkAllowed">true</item>
+        <item name="android:actionBarStyle">@style/TelecomDialerSettingsActionBarStyle</item>
+        <item name="android:windowLightNavigationBar">true</item>
+        <item name="android:windowContentOverlay">@null</item>
+        <item name="android:listDivider">@null</item>
+    </style>
+
+</resources>
\ No newline at end of file
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 b243fcd..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>
@@ -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-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 f412626..2332d4d 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -30,7 +30,7 @@
     <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 tău a folosit aplicația Telefon instalată pe dispozitiv"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"Apel cu sunet dezactivat."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Meniu pentru dezvoltatori de telecomunicații"</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 ff8de53..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>
@@ -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 a801a5c..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>
@@ -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-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 b2a6c8b..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>
@@ -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 b9185ed..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>
@@ -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/config.xml b/res/values/config.xml
index b0e50b0..15f765b 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -73,4 +73,8 @@
     <!-- When set, Telecom will attempt to bind to the {@link CallDiagnosticService} implementation
          defined by the app with this package name. -->
     <string name="call_diagnostic_service_package_name"></string>
+
+    <!-- When true, the options in the call blocking settings to block unavailable and unknown
+     callers are combined into a single toggle. -->
+    <bool name="combine_options_to_block_unavailable_and_unknown_callers">true</bool>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 6b8c0b2..d67df4b 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -73,8 +73,8 @@
          that a call be put into the background so that the app can access the audio from the call.
          [CHAR LIMIT=NONE] -->
     <string name="notification_audioProcessing_body">
-        <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.
+        <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>
 
     <!-- Crashed in call service notification label, used when the in call service has crashed and
@@ -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/res/values/styles.xml b/res/values/styles.xml
index 53e1bcb..c8b24d3 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -29,19 +29,21 @@
    <style name="Theme.Telecom.DialerSettings" parent="@android:style/Theme.DeviceDefault.Light">
         <item name="android:forceDarkAllowed">true</item>
         <item name="android:actionBarStyle">@style/TelecomDialerSettingsActionBarStyle</item>
-        <item name="android:actionOverflowButtonStyle">@style/TelecomDialerSettingsActionOverflowButtonStyle</item>
+        <item name="android:navigationBarColor">@android:color/transparent</item>
         <item name="android:windowLightStatusBar">true</item>
         <item name="android:windowLightNavigationBar">true</item>
+    </style>
+
+    <style name="Theme.Telecom.EnableAccount" parent="Theme.Telecom.DialerSettings">
+        <item name="android:actionOverflowButtonStyle">
+            @style/TelecomDialerSettingsActionOverflowButtonStyle
+        </item>
         <item name="android:windowContentOverlay">@null</item>
     </style>
 
-    <style name="Theme.Telecom.BlockedNumbers" parent="@android:style/Theme.DeviceDefault.Light">
-        <item name="android:forceDarkAllowed">true</item>
-        <item name="android:actionBarStyle">@style/TelecomDialerSettingsActionBarStyle</item>
-        <item name="android:windowLightStatusBar">true</item>
-        <item name="android:windowLightNavigationBar">true</item>
-        <item name="android:windowContentOverlay">@null</item>
+    <style name="Theme.Telecom.BlockedNumbers" parent="Theme.Telecom.DialerSettings">
         <item name="android:listDivider">@null</item>
+        <item name="android:windowContentOverlay">@null</item>
     </style>
 
     <style name="TelecomDialerSettingsActionBarStyle" parent="android:Widget.DeviceDefault.ActionBar">
diff --git a/res/xml/activity_blocked_numbers.xml b/res/xml/activity_blocked_numbers.xml
index f884ec9..e77184d 100644
--- a/res/xml/activity_blocked_numbers.xml
+++ b/res/xml/activity_blocked_numbers.xml
@@ -70,6 +70,8 @@
                             android:layout_height="wrap_content"
                             android:text="@string/blocked_numbers_msg"
                             android:paddingBottom="@dimen/blocked_numbers_extra_large_padding"
+                            android:clickable="false"
+                            android:longClickable="false"
                             style="@style/BlockedNumbersTextPrimary2" />
 
                     <TextView
diff --git a/res/xml/add_blocked_number_dialog.xml b/res/xml/add_blocked_number_dialog.xml
index 35ab633..c344280 100644
--- a/res/xml/add_blocked_number_dialog.xml
+++ b/res/xml/add_blocked_number_dialog.xml
@@ -28,6 +28,8 @@
             android:text="@string/add_blocked_dialog_body"
             android:paddingBottom="@dimen/blocked_numbers_large_padding"
             android:gravity="start"
+            android:clickable="false"
+            android:longClickable="false"
             style="@style/BlockedNumbersTextPrimary2" />
     <EditText
             android:id="@+id/add_blocked_number"
diff --git a/src/com/android/server/telecom/Analytics.java b/src/com/android/server/telecom/Analytics.java
index d6780ed..bbcf858 100644
--- a/src/com/android/server/telecom/Analytics.java
+++ b/src/com/android/server/telecom/Analytics.java
@@ -16,6 +16,8 @@
 
 package com.android.server.telecom;
 
+import static java.util.Map.entry;
+
 import android.content.Context;
 import android.os.SystemProperties;
 
@@ -48,6 +50,16 @@
 import java.util.concurrent.LinkedBlockingDeque;
 import java.util.stream.Collectors;
 
+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 static android.provider.CallLog.Calls.USER_MISSED_DND_MODE;
+import static android.provider.CallLog.Calls.USER_MISSED_LOW_RING_VOLUME;
+import static android.provider.CallLog.Calls.USER_MISSED_NEVER_RANG;
+import static android.provider.CallLog.Calls.USER_MISSED_NO_VIBRATE;
+import static android.provider.CallLog.Calls.USER_MISSED_SHORT_RING;
 import static android.telecom.ParcelableCallAnalytics.AnalyticsEvent;
 import static android.telecom.TelecomAnalytics.SessionTiming;
 
@@ -59,103 +71,102 @@
     public static final String ANALYTICS_DUMPSYS_ARG = "analytics";
     private static final String CLEAR_ANALYTICS_ARG = "clear";
 
-    public static final Map<String, Integer> sLogEventToAnalyticsEvent =
-            new HashMap<String, Integer>() {{
-                put(LogUtils.Events.SET_SELECT_PHONE_ACCOUNT,
-                        AnalyticsEvent.SET_SELECT_PHONE_ACCOUNT);
-                put(LogUtils.Events.REQUEST_HOLD, AnalyticsEvent.REQUEST_HOLD);
-                put(LogUtils.Events.REQUEST_UNHOLD, AnalyticsEvent.REQUEST_UNHOLD);
-                put(LogUtils.Events.SWAP, AnalyticsEvent.SWAP);
-                put(LogUtils.Events.SKIP_RINGING, AnalyticsEvent.SKIP_RINGING);
-                put(LogUtils.Events.CONFERENCE_WITH, AnalyticsEvent.CONFERENCE_WITH);
-                put(LogUtils.Events.SPLIT_FROM_CONFERENCE, AnalyticsEvent.SPLIT_CONFERENCE);
-                put(LogUtils.Events.SET_PARENT, AnalyticsEvent.SET_PARENT);
-                put(LogUtils.Events.MUTE, AnalyticsEvent.MUTE);
-                put(LogUtils.Events.UNMUTE, AnalyticsEvent.UNMUTE);
-                put(LogUtils.Events.AUDIO_ROUTE_BT, AnalyticsEvent.AUDIO_ROUTE_BT);
-                put(LogUtils.Events.AUDIO_ROUTE_EARPIECE, AnalyticsEvent.AUDIO_ROUTE_EARPIECE);
-                put(LogUtils.Events.AUDIO_ROUTE_HEADSET, AnalyticsEvent.AUDIO_ROUTE_HEADSET);
-                put(LogUtils.Events.AUDIO_ROUTE_SPEAKER, AnalyticsEvent.AUDIO_ROUTE_SPEAKER);
-                put(LogUtils.Events.SILENCE, AnalyticsEvent.SILENCE);
-                put(LogUtils.Events.SCREENING_COMPLETED, AnalyticsEvent.SCREENING_COMPLETED);
-                put(LogUtils.Events.BLOCK_CHECK_FINISHED, AnalyticsEvent.BLOCK_CHECK_FINISHED);
-                put(LogUtils.Events.DIRECT_TO_VM_FINISHED, AnalyticsEvent.DIRECT_TO_VM_FINISHED);
-                put(LogUtils.Events.REMOTELY_HELD, AnalyticsEvent.REMOTELY_HELD);
-                put(LogUtils.Events.REMOTELY_UNHELD, AnalyticsEvent.REMOTELY_UNHELD);
-                put(LogUtils.Events.REQUEST_PULL, AnalyticsEvent.REQUEST_PULL);
-                put(LogUtils.Events.REQUEST_ACCEPT, AnalyticsEvent.REQUEST_ACCEPT);
-                put(LogUtils.Events.REQUEST_REJECT, AnalyticsEvent.REQUEST_REJECT);
-                put(LogUtils.Events.SET_ACTIVE, AnalyticsEvent.SET_ACTIVE);
-                put(LogUtils.Events.SET_DISCONNECTED, AnalyticsEvent.SET_DISCONNECTED);
-                put(LogUtils.Events.SET_HOLD, AnalyticsEvent.SET_HOLD);
-                put(LogUtils.Events.SET_DIALING, AnalyticsEvent.SET_DIALING);
-                put(LogUtils.Events.START_CONNECTION, AnalyticsEvent.START_CONNECTION);
-                put(LogUtils.Events.BIND_CS, AnalyticsEvent.BIND_CS);
-                put(LogUtils.Events.CS_BOUND, AnalyticsEvent.CS_BOUND);
-                put(LogUtils.Events.SCREENING_SENT, AnalyticsEvent.SCREENING_SENT);
-                put(LogUtils.Events.DIRECT_TO_VM_INITIATED, AnalyticsEvent.DIRECT_TO_VM_INITIATED);
-                put(LogUtils.Events.BLOCK_CHECK_INITIATED, AnalyticsEvent.BLOCK_CHECK_INITIATED);
-                put(LogUtils.Events.FILTERING_INITIATED, AnalyticsEvent.FILTERING_INITIATED);
-                put(LogUtils.Events.FILTERING_COMPLETED, AnalyticsEvent.FILTERING_COMPLETED);
-                put(LogUtils.Events.FILTERING_TIMED_OUT, AnalyticsEvent.FILTERING_TIMED_OUT);
-            }};
+    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.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 =
-            new HashMap<String, Integer> () {{
-                put(LogUtils.Sessions.ICA_ANSWER_CALL, SessionTiming.ICA_ANSWER_CALL);
-                put(LogUtils.Sessions.ICA_REJECT_CALL, SessionTiming.ICA_REJECT_CALL);
-                put(LogUtils.Sessions.ICA_DISCONNECT_CALL, SessionTiming.ICA_DISCONNECT_CALL);
-                put(LogUtils.Sessions.ICA_HOLD_CALL, SessionTiming.ICA_HOLD_CALL);
-                put(LogUtils.Sessions.ICA_UNHOLD_CALL, SessionTiming.ICA_UNHOLD_CALL);
-                put(LogUtils.Sessions.ICA_MUTE, SessionTiming.ICA_MUTE);
-                put(LogUtils.Sessions.ICA_SET_AUDIO_ROUTE, SessionTiming.ICA_SET_AUDIO_ROUTE);
-                put(LogUtils.Sessions.ICA_CONFERENCE, SessionTiming.ICA_CONFERENCE);
-                put(LogUtils.Sessions.CSW_HANDLE_CREATE_CONNECTION_COMPLETE,
-                        SessionTiming.CSW_HANDLE_CREATE_CONNECTION_COMPLETE);
-                put(LogUtils.Sessions.CSW_SET_ACTIVE, SessionTiming.CSW_SET_ACTIVE);
-                put(LogUtils.Sessions.CSW_SET_RINGING, SessionTiming.CSW_SET_RINGING);
-                put(LogUtils.Sessions.CSW_SET_DIALING, SessionTiming.CSW_SET_DIALING);
-                put(LogUtils.Sessions.CSW_SET_DISCONNECTED, SessionTiming.CSW_SET_DISCONNECTED);
-                put(LogUtils.Sessions.CSW_SET_ON_HOLD, SessionTiming.CSW_SET_ON_HOLD);
-                put(LogUtils.Sessions.CSW_REMOVE_CALL, SessionTiming.CSW_REMOVE_CALL);
-                put(LogUtils.Sessions.CSW_SET_IS_CONFERENCED, SessionTiming.CSW_SET_IS_CONFERENCED);
-                put(LogUtils.Sessions.CSW_ADD_CONFERENCE_CALL,
-                        SessionTiming.CSW_ADD_CONFERENCE_CALL);
+    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));
 
-            }};
-
-    public static final Map<String, Integer> sLogEventTimingToAnalyticsEventTiming =
-            new HashMap<String, Integer>() {{
-                put(LogUtils.Events.Timings.ACCEPT_TIMING,
-                        ParcelableCallAnalytics.EventTiming.ACCEPT_TIMING);
-                put(LogUtils.Events.Timings.REJECT_TIMING,
-                        ParcelableCallAnalytics.EventTiming.REJECT_TIMING);
-                put(LogUtils.Events.Timings.DISCONNECT_TIMING,
-                        ParcelableCallAnalytics.EventTiming.DISCONNECT_TIMING);
-                put(LogUtils.Events.Timings.HOLD_TIMING,
-                        ParcelableCallAnalytics.EventTiming.HOLD_TIMING);
-                put(LogUtils.Events.Timings.UNHOLD_TIMING,
-                        ParcelableCallAnalytics.EventTiming.UNHOLD_TIMING);
-                put(LogUtils.Events.Timings.OUTGOING_TIME_TO_DIALING_TIMING,
-                        ParcelableCallAnalytics.EventTiming.OUTGOING_TIME_TO_DIALING_TIMING);
-                put(LogUtils.Events.Timings.BIND_CS_TIMING,
-                        ParcelableCallAnalytics.EventTiming.BIND_CS_TIMING);
-                put(LogUtils.Events.Timings.SCREENING_COMPLETED_TIMING,
-                        ParcelableCallAnalytics.EventTiming.SCREENING_COMPLETED_TIMING);
-                put(LogUtils.Events.Timings.DIRECT_TO_VM_FINISHED_TIMING,
-                        ParcelableCallAnalytics.EventTiming.DIRECT_TO_VM_FINISHED_TIMING);
-                put(LogUtils.Events.Timings.BLOCK_CHECK_FINISHED_TIMING,
-                        ParcelableCallAnalytics.EventTiming.BLOCK_CHECK_FINISHED_TIMING);
-                put(LogUtils.Events.Timings.FILTERING_COMPLETED_TIMING,
-                        ParcelableCallAnalytics.EventTiming.FILTERING_COMPLETED_TIMING);
-                put(LogUtils.Events.Timings.FILTERING_TIMED_OUT_TIMING,
-                        ParcelableCallAnalytics.EventTiming.FILTERING_TIMED_OUT_TIMING);
-                put(LogUtils.Events.Timings.START_CONNECTION_TO_REQUEST_DISCONNECT_TIMING,
-                        ParcelableCallAnalytics.EventTiming.
-                                START_CONNECTION_TO_REQUEST_DISCONNECT_TIMING);
-            }};
+    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.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());
@@ -222,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
@@ -426,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
@@ -492,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()
@@ -541,8 +552,30 @@
         }
 
         private String getMissedReasonString() {
-            //TODO: Implement this
-            return null;
+            StringBuilder s =  new StringBuilder();
+            s.append('[');
+            if ((missedReason & AUTO_MISSED_EMERGENCY_CALL) != 0) {
+                s.append("emergency]");
+                return s.toString();
+            } else if ((missedReason & AUTO_MISSED_MAXIMUM_DIALING) != 0) {
+                s.append("max_dialing]");
+                return s.toString();
+            } else if ((missedReason & AUTO_MISSED_MAXIMUM_RINGING) != 0) {
+                s.append("max_ringing]");
+                return s.toString();
+            }
+
+            // user missed
+            if ((missedReason & USER_MISSED_SHORT_RING) != 0) s.append("short_ring ");
+            if ((missedReason & USER_MISSED_DND_MODE) != 0) s.append("dnd ");
+            if ((missedReason & USER_MISSED_LOW_RING_VOLUME) != 0) s.append("low_volume ");
+            if ((missedReason & USER_MISSED_NO_VIBRATE) != 0) s.append("no_vibrate ");
+            if ((missedReason & USER_MISSED_CALL_SCREENING_SERVICE_SILENCED) != 0)
+                s.append("css_silenced ");
+            if ((missedReason & USER_MISSED_CALL_FILTERS_TIMEOUT) != 0) s.append("filter_timeout ");
+            if ((missedReason & USER_MISSED_NEVER_RANG) != 0) s.append("no_ring ");
+            s.append("]");
+            return s.toString();
         }
 
         private String getInCallServicesString() {
@@ -579,6 +612,7 @@
             }
         }
     }
+
     public static final String TAG = "TelecomAnalytics";
 
     // Constants for call direction
@@ -813,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 5c9ca84..9426100 100644
--- a/src/com/android/server/telecom/CallScreeningServiceHelper.java
+++ b/src/com/android/server/telecom/CallScreeningServiceHelper.java
@@ -227,9 +227,11 @@
         if (context.bindServiceAsUser(
                 intent,
                 serviceConnection,
-                Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
-                UserHandle.CURRENT)) {
-            Log.d(TAG, "bindService, found service, waiting for it to connect");
+                Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
+                | Context.BIND_SCHEDULE_LIKE_TOP_APP,
+                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 73cdd5b..d775350
--- 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;
@@ -2298,17 +2568,37 @@
                 mPendingRedirectedOutgoingCallInfo.remove(pendingCallId);
                 mPendingUnredirectedOutgoingCallInfo.remove(pendingCallId);
             }
+            switch (action) {
+                case TelecomBroadcastIntentProcessor.ACTION_PLACE_REDIRECTED_CALL: {
+                    Runnable r = mPendingRedirectedOutgoingCallInfo.get(callId);
+                    if (r != null) {
+                        mHandler.post(r.prepare());
+                    } else {
+                        Log.w(this, "Processing %s for canceled Call ID %s",
+                                action, callId);
+                    }
+                    break;
+                }
+                case TelecomBroadcastIntentProcessor.ACTION_PLACE_UNREDIRECTED_CALL: {
+                    Runnable r = mPendingUnredirectedOutgoingCallInfo.get(callId);
+                    if (r != null) {
+                        mHandler.post(r.prepare());
+                    } else {
+                        Log.w(this, "Processing %s for canceled Call ID %s",
+                                action, callId);
+                    }
+                    break;
+                }
+                case TelecomBroadcastIntentProcessor.ACTION_CANCEL_REDIRECTED_CALL: {
+                    Log.addEvent(mPendingRedirectedOutgoingCall,
+                            LogUtils.Events.REDIRECTION_USER_CANCELLED);
+                    mPendingRedirectedOutgoingCall.disconnect("User canceled the redirected call.");
+                    break;
+                }
+                default: {
+                    // Unexpected, ignore
+                }
 
-            if (action.equals(TelecomBroadcastIntentProcessor.ACTION_PLACE_REDIRECTED_CALL)) {
-                mHandler.post(mPendingRedirectedOutgoingCallInfo.get(callId).prepare());
-            } else if (action.equals(
-                    TelecomBroadcastIntentProcessor.ACTION_PLACE_UNREDIRECTED_CALL)) {
-                mHandler.post(mPendingUnredirectedOutgoingCallInfo.get(callId).prepare());
-            } else if (action.equals(
-                    TelecomBroadcastIntentProcessor.ACTION_CANCEL_REDIRECTED_CALL)) {
-                Log.addEvent(mPendingRedirectedOutgoingCall,
-                        LogUtils.Events.REDIRECTION_USER_CANCELLED);
-                mPendingRedirectedOutgoingCall.disconnect("User canceled the redirected call.");
             }
             mPendingRedirectedOutgoingCall = null;
             mPendingRedirectedOutgoingCallInfo.remove(callId);
@@ -2400,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"));
@@ -2436,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();
@@ -2829,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;
         }
@@ -2857,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) {
 
@@ -2871,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;
@@ -2917,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)) {
@@ -2956,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();
@@ -3014,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.
@@ -3033,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);
@@ -3079,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
@@ -3104,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());
@@ -3138,7 +3539,6 @@
         }
     }
 
-    @VisibleForTesting
     public void markCallAsOnHold(Call call) {
         setCallState(call, CallState.ON_HOLD, "on-hold set explicitly");
     }
@@ -3149,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) {
@@ -3175,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.
@@ -3219,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
@@ -3244,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();
+        }
     }
 
     /**
@@ -3345,10 +3776,6 @@
         return false;
     }
 
-    boolean hasActiveOrHoldingCall() {
-        return getFirstCallWithState(CallState.ACTIVE, CallState.ON_HOLD) != null;
-    }
-
     boolean hasRingingCall() {
         return getFirstCallWithState(CallState.RINGING, CallState.ANSWERED) != null;
     }
@@ -3470,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(),
@@ -3602,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(),
@@ -3611,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();
@@ -3696,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);
@@ -3728,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();
@@ -4081,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);
@@ -4175,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;
     }
@@ -4272,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;
         }
 
@@ -4282,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)) {
@@ -4353,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;
     }
 
@@ -4381,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;
         }
@@ -4398,6 +4901,7 @@
                         + " of new outgoing call.");
                 return true;
             }
+            call.setStartFailCause(CallFailureCause.MAX_OUTGOING_CALLS);
             return false;
         }
 
@@ -4446,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;
     }
 
@@ -4489,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();
+            }
+        });
     }
 
     /**
@@ -4547,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;
@@ -4566,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
@@ -4670,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) {
@@ -4858,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: ");
@@ -4914,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();
@@ -4946,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());
@@ -5019,6 +5591,9 @@
         } else {
             call.setConnectionService(service);
             service.createConnectionFailed(call);
+            if (!mCalls.contains(call)){
+                mListeners.forEach(l -> l.onCreateConnectionFailed(call));
+            }
         }
     }
 
@@ -5041,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
@@ -5207,7 +5793,7 @@
                 // Disconnect all self-managed calls to make priority for emergency call.
                 disconnectSelfManagedCalls("emergency call");
             }
-
+            notifyStartCreateConnection(call);
             call.startCreateConnection(mPhoneAccountRegistrar);
         }
 
@@ -5384,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);
     }
 
@@ -5392,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) {
@@ -5415,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);
+                }
             }
         }
     }
@@ -5497,6 +6089,82 @@
         }
     }
 
+    /**
+     * This helper mainly requests mConnectionSvrFocusMgr to update the call focus via a
+     * {@link TransactionalFocusRequestCallback}.  However, in the case of a held call, the
+     * state must be set first and then a request must be made.
+     *
+     * @param newCallFocus          to set active/answered
+     * @param resultCallback        that back propagates the focusManager result
+     *
+     * Note: This method should only be called if there are no active calls.
+     */
+    public void requestNewCallFocusAndVerify(Call newCallFocus,
+            OutcomeReceiver<Boolean, CallException> resultCallback) {
+        int currentCallState = newCallFocus.getState();
+        PendingAction pendingAction = null;
+
+        // if the current call is in a state that can become the new call focus, we can set the
+        // state afterwards...
+        if (ConnectionServiceFocusManager.PRIORITY_FOCUS_CALL_STATE.contains(currentCallState)) {
+            pendingAction = new ActionSetCallState(newCallFocus, CallState.ACTIVE,
+                    "vCFC: pending action set state");
+        } else {
+            // However, HELD calls need to be set to ACTIVE before requesting call focus.
+            setCallState(newCallFocus, CallState.ACTIVE, "vCFC: immediately set active");
+        }
+
+        mConnectionSvrFocusMgr
+                .requestFocus(newCallFocus,
+                        new TransactionalFocusRequestCallback(pendingAction, currentCallState,
+                                newCallFocus, resultCallback));
+    }
+
+    /**
+     * Request a new call focus and ensure the request was successful via an OutcomeReceiver. Also,
+     * conditionally include a PendingAction that will execute if and only if the call focus change
+     * is successful.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public class TransactionalFocusRequestCallback implements
+            ConnectionServiceFocusManager.RequestFocusCallback {
+        private PendingAction mPendingAction;
+        private int mPreviousCallState;
+        @NonNull private Call mTargetCallFocus;
+        private OutcomeReceiver<Boolean, CallException> mCallback;
+
+        TransactionalFocusRequestCallback(PendingAction pendingAction, int previousState,
+                @NonNull Call call, OutcomeReceiver<Boolean, CallException> callback) {
+            mPendingAction = pendingAction;
+            mPreviousCallState = previousState;
+            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())) {
+                // possibly reset the call state
+                if (mTargetCallFocus.getState() != mPreviousCallState) {
+                    mTargetCallFocus.setState(mPreviousCallState, "resetting call state");
+                }
+                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
+            if (mPendingAction != null) {
+                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());
@@ -5516,7 +6184,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;
     }
@@ -5565,6 +6234,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
@@ -5645,4 +6327,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..6fbc494 100644
--- a/src/com/android/server/telecom/ConnectionServiceFocusManager.java
+++ b/src/com/android/server/telecom/ConnectionServiceFocusManager.java
@@ -32,6 +32,7 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.Set;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
@@ -153,9 +154,9 @@
         void setCallsManagerListener(CallsManager.CallsManagerListener listener);
     }
 
-    private static final int[] PRIORITY_FOCUS_CALL_STATE = new int[] {
-            CallState.ACTIVE, CallState.CONNECTING, CallState.DIALING, CallState.AUDIO_PROCESSING
-    };
+    public static final Set<Integer> PRIORITY_FOCUS_CALL_STATE
+            = Set.of(CallState.ACTIVE, CallState.CONNECTING, CallState.DIALING,
+            CallState.AUDIO_PROCESSING, CallState.RINGING);
 
     private static final int MSG_REQUEST_FOCUS = 1;
     private static final int MSG_RELEASE_CONNECTION_FOCUS = 2;
@@ -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;
         }
 
@@ -371,17 +374,15 @@
                         && call.isFocusable())
                 .collect(Collectors.toList());
 
-        for (int i = 0; i < PRIORITY_FOCUS_CALL_STATE.length; i++) {
-            for (CallFocus call : calls) {
-                if (call.getState() == PRIORITY_FOCUS_CALL_STATE[i]) {
-                    mCurrentFocusCall = call;
-                    Log.d(this, "updateCurrentFocusCall %s", mCurrentFocusCall);
-                    return;
-                }
+        for (CallFocus call : calls) {
+            if (PRIORITY_FOCUS_CALL_STATE.contains(call.getState())) {
+                mCurrentFocusCall = call;
+                Log.i(this, "updateCurrentFocusCall %s", mCurrentFocusCall);
+                return;
             }
         }
 
-        Log.d(this, "updateCurrentFocusCall = null");
+        Log.i(this, "updateCurrentFocusCall = null");
     }
 
     private void onRequestFocusDone(FocusRequest focusRequest) {
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 ec87555..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,10 +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,
-                    UserHandle.CURRENT)) {
+                        | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS
+                        | Context.BIND_SCHEDULE_LIKE_TOP_APP, userToBind)) {
                 Log.w(this, "Failed to connect.");
                 mIsConnected = false;
             }
@@ -344,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
@@ -356,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(
@@ -367,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);
@@ -393,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
@@ -404,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);
         }
     }
 
@@ -461,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) {
@@ -471,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.
@@ -611,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)
@@ -786,7 +818,7 @@
 
         @Override
         public void onConnectionPropertiesChanged(Call call, boolean didRttChange) {
-            updateCall(call, false /* includeVideoProvider */, didRttChange);
+            updateCall(call, false /* includeVideoProvider */, didRttChange, null);
         }
 
         @Override
@@ -796,7 +828,7 @@
 
         @Override
         public void onVideoCallProviderChanged(Call call) {
-            updateCall(call, true /* videoProviderChanged */, false);
+            updateCall(call, true /* videoProviderChanged */, false, null);
         }
 
         @Override
@@ -804,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);
         }
 
         /**
@@ -893,7 +935,7 @@
         @Override
         public void onRttInitiationFailure(Call call, int reason) {
             notifyRttInitiationFailure(call, reason);
-            updateCall(call, false, true);
+            updateCall(call, false, true, null);
         }
 
         @Override
@@ -916,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))
@@ -924,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
@@ -987,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);
 
@@ -1001,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();
 
@@ -1135,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);
         }
     }
 
@@ -1203,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();
                     }
@@ -1226,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()) {
@@ -1247,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(),
@@ -1266,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());
     }
@@ -1320,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");
+                    }
+                }
+            });
         }
     }
 
@@ -1333,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) {
@@ -1405,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);
@@ -1419,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) {
@@ -1432,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"),
@@ -1459,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",
@@ -1475,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",
@@ -1491,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) {
@@ -1502,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) {
@@ -1515,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);
     }
 
     /**
@@ -1542,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(
@@ -1553,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,
@@ -1593,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));
@@ -1604,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);
@@ -1634,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]",
@@ -1649,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 {
@@ -1666,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);
@@ -1707,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) {
@@ -1727,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) {
@@ -1779,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 &&
@@ -1832,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;
         }
@@ -1851,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);
         }
     }
 
@@ -1868,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
@@ -1877,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(
@@ -1888,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;
         }
@@ -1923,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.
@@ -1952,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);
+        }
     }
 
     /**
@@ -1968,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;
                 }
@@ -2000,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);
 
@@ -2040,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();
     }
 
     /**
@@ -2060,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();
 
@@ -2078,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",
@@ -2103,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);
@@ -2118,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>");
@@ -2150,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
@@ -2180,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;
     }
 
@@ -2230,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();
                 }
             }
         }
@@ -2302,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);
@@ -2347,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();
@@ -2375,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) {
@@ -2385,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 4665ec2..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;
@@ -524,7 +518,7 @@
         mMainThreadHandler.post(new Runnable("ICTP.cUTP", mLock) {
             @Override
             public void loggedRun() {
-                int newToneCount = sTonesPlaying.updateAndGet( t -> Math.min(0, t--));
+                int newToneCount = sTonesPlaying.updateAndGet( t -> Math.max(0, --t));
 
                 if (newToneCount == 0) {
                     Log.i(InCallTonePlayer.this,
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 7b24a09..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
@@ -287,6 +288,12 @@
         if (account != null) {
             return defaultPhoneAccountHandle.phoneAccountHandle;
         }
+
+        Log.v(this,
+                "getUserSelectedOutgoingPhoneAccount: defaultPhoneAccountHandle"
+                        + ".phoneAccountHandle=[%s] is not registered or owned by %s"
+                , defaultPhoneAccountHandle.phoneAccountHandle, userHandle);
+
         return null;
     }
 
@@ -317,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(),
@@ -343,6 +350,15 @@
                 mState.defaultOutgoingAccountHandles.get(userHandle);
         PhoneAccountHandle currentDefaultPhoneAccount = currentDefaultInfo == null ? null :
                 currentDefaultInfo.phoneAccountHandle;
+
+        Log.i(this, "setUserSelectedOutgoingPhoneAccount: %s", accountHandle);
+
+        if (Objects.equals(currentDefaultPhoneAccount, accountHandle)) {
+            Log.i(this, "setUserSelectedOutgoingPhoneAccount: "
+                    + "no change in default phoneAccountHandle.  current is same as new.");
+            return;
+        }
+
         boolean isSimAccount = false;
         if (accountHandle == null) {
             // Asking to clear the default outgoing is a valid request
@@ -371,24 +387,21 @@
                     .put(userHandle, new DefaultPhoneAccountHandle(userHandle, accountHandle,
                             account.getGroupId()));
         }
-        Log.i(this, "setUserSelectedOutgoingPhoneAccount: %s", accountHandle);
 
         // Potentially update the default voice subid in SubscriptionManager.
-        if (!Objects.equals(currentDefaultPhoneAccount, accountHandle)) {
-            int newSubId = accountHandle == null ? SubscriptionManager.INVALID_SUBSCRIPTION_ID :
-                    getSubscriptionIdForPhoneAccount(accountHandle);
-            if (isSimAccount || accountHandle == null) {
-                int currentVoiceSubId = mSubscriptionManager.getDefaultVoiceSubscriptionId();
-                if (newSubId != currentVoiceSubId) {
-                    Log.i(this, "setUserSelectedOutgoingPhoneAccount: update voice sub; "
-                            + "account=%s, subId=%d", accountHandle, newSubId);
-                    mSubscriptionManager.setDefaultVoiceSubscriptionId(newSubId);
-                }
+        int newSubId = accountHandle == null ? SubscriptionManager.INVALID_SUBSCRIPTION_ID :
+                getSubscriptionIdForPhoneAccount(accountHandle);
+        if (isSimAccount || accountHandle == null) {
+            int currentVoiceSubId = mSubscriptionManager.getDefaultVoiceSubscriptionId();
+            if (newSubId != currentVoiceSubId) {
+                Log.i(this, "setUserSelectedOutgoingPhoneAccount: update voice sub; "
+                        + "account=%s, subId=%d", accountHandle, newSubId);
+                mSubscriptionManager.setDefaultVoiceSubscriptionId(newSubId);
             } else {
-                Log.i(this, "setUserSelectedOutgoingPhoneAccount: %s is not a sub", accountHandle);
+                Log.i(this, "setUserSelectedOutgoingPhoneAccount: no change to voice sub");
             }
         } else {
-            Log.i(this, "setUserSelectedOutgoingPhoneAccount: no change to voice sub");
+            Log.i(this, "setUserSelectedOutgoingPhoneAccount: %s is not a sub", accountHandle);
         }
 
         write();
@@ -457,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();
 
@@ -713,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);
     }
 
     /**
@@ -736,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);
     }
 
     /**
@@ -755,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);
     }
 
     /**
@@ -777,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);
     }
 
     /**
@@ -791,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() {
@@ -806,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 */);
     }
 
     /**
@@ -847,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");
+            }
+        }
     }
 
     /**
@@ -892,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);
@@ -1172,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);
         }
     }
@@ -1190,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) {
@@ -1208,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
     //
@@ -1254,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);
     }
 
     /**
@@ -1269,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;
@@ -1285,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);
     }
 
     /**
@@ -1307,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)) {
@@ -1330,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;
             }
@@ -1339,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;
             }
@@ -1766,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/RespondViaSmsManager.java b/src/com/android/server/telecom/RespondViaSmsManager.java
index 23ccc1c..8507703 100644
--- a/src/com/android/server/telecom/RespondViaSmsManager.java
+++ b/src/com/android/server/telecom/RespondViaSmsManager.java
@@ -31,11 +31,13 @@
 import android.telephony.PhoneNumberUtils;
 import android.telephony.SmsManager;
 import android.telephony.SubscriptionManager;
+import android.text.BidiFormatter;
 import android.text.Spannable;
 import android.text.SpannableString;
 import android.text.TextUtils;
 import android.widget.Toast;
 
+import java.text.Bidi;
 import java.util.ArrayList;
 import java.util.List;
 
@@ -158,7 +160,9 @@
         final String formatString = res.getString(success
                 ? R.string.respond_via_sms_confirmation_format
                 : R.string.respond_via_sms_failure_format);
-        final String confirmationMsg = String.format(formatString, phoneNumber);
+        final BidiFormatter phoneNumberFormatter = BidiFormatter.getInstance();
+        final String confirmationMsg = String.format(formatString,
+                phoneNumberFormatter.unicodeWrap(phoneNumber));
         int startingPosition = confirmationMsg.indexOf(phoneNumber);
         int endingPosition = startingPosition + phoneNumber.length();
 
@@ -207,10 +211,12 @@
                         PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE);
                 sentIntents.add(pendingIntent);
             }
+
             MessageSentReceiver receiver = new MessageSentReceiver(
                     !TextUtils.isEmpty(contactName) ? contactName : phoneNumber,
                     messageParts.size());
-            context.registerReceiver(receiver, new IntentFilter(ACTION_MESSAGE_SENT));
+            context.registerReceiver(receiver, new IntentFilter(ACTION_MESSAGE_SENT),
+                    Context.RECEIVER_NOT_EXPORTED);
             smsManager.sendMultipartTextMessage(phoneNumber, null, messageParts,
                     sentIntents/*sentIntent*/, null /*deliveryIntent*/, context.getOpPackageName(),
                     context.getAttributionTag());
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 b29b500..b6aa4cc 100644
--- a/src/com/android/server/telecom/Ringer.java
+++ b/src/com/android/server/telecom/Ringer.java
@@ -19,7 +19,9 @@
 import static android.provider.CallLog.Calls.USER_MISSED_DND_MODE;
 import static android.provider.CallLog.Calls.USER_MISSED_LOW_RING_VOLUME;
 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;
@@ -31,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;
@@ -46,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);
@@ -151,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
@@ -188,7 +205,9 @@
             RingtoneFactory ringtoneFactory,
             Vibrator vibrator,
             VibrationEffectProxy vibrationEffectProxy,
-            InCallController inCallController) {
+            InCallController inCallController,
+            NotificationManager notificationManager,
+            AccessibilityManagerAdapter accessibilityManagerAdapter) {
 
         mLock = new Object();
         mSystemSettingsUtil = systemSettingsUtil;
@@ -201,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,
@@ -212,6 +233,8 @@
 
         mIsHapticPlaybackSupportedByDevice =
                 mSystemSettingsUtil.isHapticPlaybackSupported(mContext);
+
+        mAudioManager = mContext.getSystemService(AudioManager.class);
     }
 
     @VisibleForTesting
@@ -219,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);
-        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.getRingerModeInternal(), 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.getRingerModeInternal(), 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) {
@@ -418,7 +521,8 @@
             return;
         }
 
-        if (mInCallController.doesConnectedDialerSupportRinging()) {
+        if (mInCallController.doesConnectedDialerSupportRinging(
+                call.getUserHandleFromTargetPhoneAccount())) {
             Log.addEvent(call, LogUtils.Events.SKIP_RINGING, "Dialer handles");
             return;
         }
@@ -442,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);
@@ -450,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();
@@ -482,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) {
@@ -503,11 +625,16 @@
         }
     }
 
-    private boolean isVibratorEnabled(Context context) {
+    private boolean isVibratorEnabled(Context context, boolean shouldRingForContact) {
         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
+        boolean zenModeOn = mNotificationManager != null
+                && mNotificationManager.getZenMode() != ZEN_MODE_OFF;
         return mVibrator.hasVibrator()
                 && mSystemSettingsUtil.isRingVibrationEnabled(context)
-                && audioManager.getRingerModeInternal() != AudioManager.RINGER_MODE_SILENT;
+                && (audioManager.getRingerMode() != AudioManager.RINGER_MODE_SILENT
+                || (zenModeOn && shouldRingForContact));
     }
 
     private RingerAttributes getRingerAttributes(Call call, boolean isHfpDeviceAttached) {
@@ -518,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);
@@ -541,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) {
@@ -579,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 b1846fe..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,16 +78,16 @@
         Uri ringtoneUri = incomingCall.getRingtone();
         Ringtone ringtone = null;
 
-        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);
-            } 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;
@@ -99,43 +104,44 @@
                     Log.i(this, "getRingtone: Settings.System.DEFAULT_RINGTONE_URI is null.");
                 }
             }
+
             if (defaultRingtoneUri == null) {
                 return null;
             }
+
             try {
                 ringtone = RingtoneManager.getRingtone(
-                        contextToUse, defaultRingtoneUri, volumeShaperConfig);
-            } 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 setRingtoneAudioAttributes(ringtone);
+        return ringtone;
     }
 
-    public Ringtone getRingtone(Call incomingCall) {
-        return getRingtone(incomingCall, null);
+    private AudioAttributes getDefaultRingtoneAudioAttributes(boolean hapticChannelsMuted) {
+        return new AudioAttributes.Builder()
+            .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+            .setHapticChannelsMuted(hapticChannelsMuted)
+            .build();
     }
 
     /** 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));
-        Ringtone ringtone = RingtoneManager.getRingtone(mContext, ringtoneUri, null);
+        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);
         }
-        return setRingtoneAudioAttributes(ringtone);
-    }
-
-    private Ringtone setRingtoneAudioAttributes(Ringtone ringtone) {
-        if (ringtone != null) {
-            ringtone.setAudioAttributes(new AudioAttributes.Builder()
-                    .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
-                    .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
-                    .build());
-        }
         return ringtone;
     }
 
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..1e6403e
--- /dev/null
+++ b/src/com/android/server/telecom/TransactionalServiceWrapper.java
@@ -0,0 +1,681 @@
+/*
+ * 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.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.MaybeHoldCallForNewCallTransaction;
+import com.android.server.telecom.voip.ParallelTransaction;
+import com.android.server.telecom.voip.RequestNewActiveCallTransaction;
+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:
+                        handleCallControlNewCallFocusTransactions(call, SET_ACTIVE,
+                                false /* isAnswer */, 0/*VideoState (ignored)*/, callback);
+                        break;
+                    case ANSWER:
+                        handleCallControlNewCallFocusTransactions(call, ANSWER,
+                                true /* isAnswer */, (int) objects[0] /*VideoState*/, 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);
+            }
+        }
+
+        // The client is request their VoIP call state go ACTIVE/ANSWERED.
+        // This request is originating from the VoIP application.
+        private void handleCallControlNewCallFocusTransactions(Call call, String action,
+                boolean isAnswer, int potentiallyNewVideoState, ResultReceiver callback) {
+            mTransactionManager.addTransaction(createSetActiveTransactions(call),
+                    new OutcomeReceiver<>() {
+                        @Override
+                        public void onResult(VoipCallTransactionResult result) {
+                            Log.i(TAG, String.format(Locale.US,
+                                    "%s: onResult: callId=[%s]", action, call.getId()));
+                            if (isAnswer) {
+                                call.setVideoState(potentiallyNewVideoState);
+                            }
+                            callback.send(TELECOM_TRANSACTION_SUCCESS, new Bundle());
+                        }
+
+                        @Override
+                        public void onError(CallException exception) {
+                            Bundle extras = new Bundle();
+                            extras.putParcelable(TRANSACTION_EXCEPTION_KEY, exception);
+                            callback.send(exception == null ? CallException.CODE_ERROR_UNKNOWN :
+                                    exception.getCode(), extras);
+                        }
+                    });
+        }
+
+        @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()));
+            handleCallEventCallbackNewFocus(call, ON_SET_ACTIVE, false /*isAnswerRequest*/,
+                    0 /*VideoState*/);
+        } 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()));
+            handleCallEventCallbackNewFocus(call, ON_ANSWER, true /*isAnswerRequest*/,
+                    videoState /*VideoState*/);
+        } finally {
+            Log.endSession();
+        }
+    }
+
+    // handle a CallEventCallback to set a call ACTIVE/ANSWERED. Must get ack from client since the
+    // request has come from another source (ex. Android Auto is requesting a call to go active)
+    private void handleCallEventCallbackNewFocus(Call call, String action, boolean isAnswerRequest,
+            int potentiallyNewVideoState) {
+        // save CallsManager state before sending client state changes
+        Call foregroundCallBeforeSwap = mCallsManager.getForegroundCall();
+        boolean wasActive = foregroundCallBeforeSwap != null && foregroundCallBeforeSwap.isActive();
+
+        SerialTransaction serialTransactions = createSetActiveTransactions(call);
+        // 3. get ack from client (that the requested call can go active)
+        if (isAnswerRequest) {
+            serialTransactions.appendTransaction(
+                    new CallEventCallbackAckTransaction(mICallEventCallback,
+                            action, call.getId(), potentiallyNewVideoState, mLock));
+        } else {
+            serialTransactions.appendTransaction(
+                    new CallEventCallbackAckTransaction(mICallEventCallback,
+                            action, call.getId(), 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()));
+                        if (isAnswerRequest) {
+                            call.setVideoState(potentiallyNewVideoState);
+                        }
+                    }
+
+                    @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<>();
+
+        // potentially hold the current active call in order to set a new call (active/answered)
+        transactions.add(new MaybeHoldCallForNewCallTransaction(mCallsManager, call));
+        // And request a new focus call update
+        transactions.add(new RequestNewActiveCallTransaction(mCallsManager, call));
+
+        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/bluetooth/BluetoothDeviceManager.java b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
index 5cc54de..473e7b9 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
@@ -27,6 +27,7 @@
 import android.content.Context;
 import android.media.AudioManager;
 import android.media.AudioDeviceInfo;
+import android.media.audio.common.AudioDevice;
 import android.telecom.Log;
 import android.util.LocalLog;
 
@@ -186,6 +187,8 @@
     private boolean mLeAudioCallbackRegistered = false;
     private BluetoothLeAudio mBluetoothLeAudioService;
     private boolean mLeAudioSetAsCommunicationDevice = false;
+    private String mLeAudioDevice;
+    private String mHearingAidDevice;
     private boolean mHearingAidSetAsCommunicationDevice = false;
     private BluetoothDevice mBluetoothHearingAidActiveDeviceCache;
     private BluetoothAdapter mBluetoothAdapter;
@@ -220,10 +223,10 @@
             }
 
             for (LinkedHashMap.Entry<BluetoothDevice, Integer> entry : mGroupsByDevice.entrySet()) {
-               if (Objects.equals(entry.getKey(),
+                if (Objects.equals(entry.getKey(),
                         mBluetoothLeAudioService.getConnectedGroupLeadDevice(entry.getValue()))) {
-                   devices.add(entry.getKey());
-               }
+                    devices.add(entry.getKey());
+                }
             }
             devices.removeIf(device -> !mLeAudioDevicesByAddress.containsValue(device));
             return devices;
@@ -417,38 +420,51 @@
     }
 
     public void clearLeAudioCommunicationDevice() {
+        Log.i(this, "clearLeAudioCommunicationDevice: mLeAudioSetAsCommunicationDevice = " +
+                mLeAudioSetAsCommunicationDevice + " device = " + mLeAudioDevice);
         if (!mLeAudioSetAsCommunicationDevice) {
             return;
         }
+        mLeAudioSetAsCommunicationDevice = false;
+        if (mLeAudioDevice != null) {
+            mBluetoothRouteManager.onAudioLost(mLeAudioDevice);
+            mLeAudioDevice = null;
+        }
 
         if (mAudioManager == null) {
             Log.i(this, "clearLeAudioCommunicationDevice: mAudioManager is null");
-            mLeAudioSetAsCommunicationDevice = false;
             return;
         }
-        if (mAudioManager.getCommunicationDevice() != null
-                && mAudioManager.getCommunicationDevice().getType()
+
+        AudioDeviceInfo audioDeviceInfo = mAudioManager.getCommunicationDevice();
+        if (audioDeviceInfo != null && audioDeviceInfo.getType()
                 == AudioDeviceInfo.TYPE_BLE_HEADSET) {
-            mBluetoothRouteManager.onAudioLost(mAudioManager.getCommunicationDevice().getAddress());
+            mBluetoothRouteManager.onAudioLost(audioDeviceInfo.getAddress());
             mAudioManager.clearCommunicationDevice();
-            mLeAudioSetAsCommunicationDevice = false;
         }
     }
 
     public void clearHearingAidCommunicationDevice() {
+        Log.i(this, "clearHearingAidCommunicationDevice: mHearingAidSetAsCommunicationDevice = "
+                + mHearingAidSetAsCommunicationDevice);
         if (!mHearingAidSetAsCommunicationDevice) {
             return;
         }
+        mHearingAidSetAsCommunicationDevice = false;
+        if (mHearingAidDevice != null) {
+            mBluetoothRouteManager.onAudioLost(mHearingAidDevice);
+            mHearingAidDevice = null;
+        }
 
         if (mAudioManager == null) {
             Log.i(this, "clearHearingAidCommunicationDevice: mAudioManager is null");
-            mHearingAidSetAsCommunicationDevice = false;
+            return;
         }
-        if (mAudioManager.getCommunicationDevice() != null
-                && mAudioManager.getCommunicationDevice().getType()
+
+        AudioDeviceInfo audioDeviceInfo = mAudioManager.getCommunicationDevice();
+        if (audioDeviceInfo != null && audioDeviceInfo.getType()
                 == AudioDeviceInfo.TYPE_HEARING_AID) {
             mAudioManager.clearCommunicationDevice();
-            mHearingAidSetAsCommunicationDevice = false;
         }
     }
 
@@ -496,6 +512,7 @@
             Log.i(this, " bleHeadset device set");
             mBluetoothRouteManager.onAudioOn(bleHeadset.getAddress());
             mLeAudioSetAsCommunicationDevice = true;
+            mLeAudioDevice = bleHeadset.getAddress();
         }
         return result;
     }
@@ -542,6 +559,7 @@
             Log.w(this, " Could not set hearingAid device");
         } else {
             Log.i(this, " hearingAid device set");
+            mHearingAidDevice = hearingAid.getAddress();
             mHearingAidSetAsCommunicationDevice = true;
         }
         return result;
@@ -549,7 +567,7 @@
 
     // Connect audio to the bluetooth device at address, checking to see whether it's
     // le audio, hearing aid or a HFP device, and using the proper BT API.
-    public boolean connectAudio(String address) {
+    public boolean connectAudio(String address, boolean switchingBtDevices) {
         if (mLeAudioDevicesByAddress.containsKey(address)) {
             if (mBluetoothLeAudioService == null) {
                 Log.w(this, "Attempting to turn on audio when the le audio service is null");
@@ -558,7 +576,15 @@
             BluetoothDevice device = mLeAudioDevicesByAddress.get(address);
             if (mBluetoothAdapter.setActiveDevice(
                     device, BluetoothAdapter.ACTIVE_DEVICE_ALL)) {
-                return setLeAudioCommunicationDevice();
+
+                /* ACTION_ACTIVE_DEVICE_CHANGED intent will trigger setting communication device.
+                 * Only after receiving ACTION_ACTIVE_DEVICE_CHANGED it is known that device that
+                 * will be audio switched to is available to be choose as communication device */
+                if (!switchingBtDevices) {
+                    return setLeAudioCommunicationDevice();
+                }
+
+                return true;
             }
             return false;
         } else if (mHearingAidDevicesByAddress.containsKey(address)) {
@@ -569,7 +595,15 @@
             if (mBluetoothAdapter.setActiveDevice(
                     mHearingAidDevicesByAddress.get(address),
                     BluetoothAdapter.ACTIVE_DEVICE_ALL)) {
-                return setHearingAidCommunicationDevice();
+
+                /* ACTION_ACTIVE_DEVICE_CHANGED intent will trigger setting communication device.
+                 * Only after receiving ACTION_ACTIVE_DEVICE_CHANGED it is known that device that
+                 * will be audio switched to is available to be choose as communication device */
+                if (!switchingBtDevices) {
+                    return setHearingAidCommunicationDevice();
+                }
+
+                return true;
             }
             return false;
         } else if (mHfpDevicesByAddress.containsKey(address)) {
@@ -612,6 +646,25 @@
         }
     }
 
+    public boolean isInbandRingingEnabled() {
+        BluetoothDevice activeDevice = mBluetoothRouteManager.getBluetoothAudioConnectedDevice();
+        Log.i(this, "isInbandRingingEnabled: activeDevice: " + activeDevice);
+        if (mBluetoothRouteManager.isCachedLeAudioDevice(activeDevice)) {
+            if (mBluetoothLeAudioService == null) {
+                Log.i(this, "isInbandRingingEnabled: no leaudio service available.");
+                return false;
+            }
+            int groupId = mBluetoothLeAudioService.getGroupId(activeDevice);
+            return mBluetoothLeAudioService.isInbandRingtoneEnabled(groupId);
+        } else {
+            if (mBluetoothHeadset == null) {
+                Log.i(this, "isInbandRingingEnabled: no headset service available.");
+                return false;
+            }
+            return mBluetoothHeadset.isInbandRingingEnabled();
+        }
+    }
+
     public void dump(IndentingPrintWriter pw) {
         mLocalLog.dump(pw);
     }
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
index bd2c6f1..7966f73 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
@@ -491,7 +491,8 @@
             SomeArgs args = (SomeArgs) msg.obj;
 
             Log.continueSession(((Session) args.arg1), "BRM.pM_" + msg.what);
-            Log.i(LOG_TAG, "Message received: %s.", MESSAGE_CODE_TO_NAME.get(msg.what));
+            Log.i(LOG_TAG, "%s received message: %s.", this,
+                    MESSAGE_CODE_TO_NAME.get(msg.what));
         } else if (msg.what == RUN_RUNNABLE && msg.obj instanceof Runnable) {
             Log.i(LOG_TAG, "Running runnable for testing");
         } else {
@@ -713,7 +714,7 @@
             return null;
         }
 
-        if (!mDeviceManager.connectAudio(actualAddress)) {
+        if (!mDeviceManager.connectAudio(actualAddress, switchingBtDevices)) {
             boolean shouldRetry = retryCount < MAX_CONNECTION_RETRIES;
             Log.w(LOG_TAG, "Could not connect to %s. Will %s", actualAddress,
                     shouldRetry ? "retry" : "not retry");
@@ -849,12 +850,7 @@
      */
     @VisibleForTesting
     public boolean isInbandRingingEnabled() {
-        BluetoothHeadset bluetoothHeadset = mDeviceManager.getBluetoothHeadset();
-        if (bluetoothHeadset == null) {
-            Log.i(this, "isInbandRingingEnabled: no headset service available.");
-            return false;
-        }
-        return bluetoothHeadset.isInbandRingingEnabled();
+        return mDeviceManager.isInbandRingingEnabled();
     }
 
     private boolean addDevice(String address) {
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 226382b..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,10 +340,10 @@
                                 mPhoneAccountHandle, mRedirectionGatewayInfo, mSpeakerphoneOn,
                                 mVideoState, mShouldCancelCall, mUiAction);
                     } else {
-                        performCarrierCallRedirection();
+                        // Use the current user for carrier call redirection
+                        performCarrierCallRedirection(UserHandle.CURRENT);
                     }
-                }
-                if (mIsCarrierRedirectionPending) {
+                } else if (mIsCarrierRedirectionPending) {
                     Log.addEvent(mCall, LogUtils.Events.REDIRECTION_COMPLETED_CARRIER);
                     mIsCarrierRedirectionPending = false;
                     mCallsManager.onCallRedirectionComplete(mCall, mDestinationUri,
@@ -357,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 {
@@ -430,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/settings/EnhancedCallBlockingFragment.java b/src/com/android/server/telecom/settings/EnhancedCallBlockingFragment.java
index c0bb56a..b1a1b0e 100644
--- a/src/com/android/server/telecom/settings/EnhancedCallBlockingFragment.java
+++ b/src/com/android/server/telecom/settings/EnhancedCallBlockingFragment.java
@@ -45,6 +45,7 @@
     private static final String BLOCK_UNAVAILABLE_NUMBERS_KEY =
             "block_unavailable_calls_setting";
     private boolean mIsCombiningRestrictedAndUnknownOption = false;
+    private boolean mIsCombiningUnavailableAndUnknownOption = false;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -94,11 +95,17 @@
                         R.bool.combine_options_to_block_restricted_and_unknown_callers);
         if (mIsCombiningRestrictedAndUnknownOption) {
             Preference restricted_pref = findPreference(BLOCK_RESTRICTED_NUMBERS_KEY);
-            Preference unavailable_pref = findPreference(BLOCK_UNAVAILABLE_NUMBERS_KEY);
             screen.removePreference(restricted_pref);
-            screen.removePreference(unavailable_pref);
             Log.i(this, "onCreate: removed block restricted preference.");
         }
+
+        mIsCombiningUnavailableAndUnknownOption = getResources().getBoolean(
+                R.bool.combine_options_to_block_unavailable_and_unknown_callers);
+        if (mIsCombiningUnavailableAndUnknownOption) {
+            Preference unavailable_pref = findPreference(BLOCK_UNAVAILABLE_NUMBERS_KEY);
+            screen.removePreference(unavailable_pref);
+            Log.i(this, "onCreate: removed block unavailable preference.");
+        }
     }
 
     /**
@@ -136,14 +143,20 @@
 
     @Override
     public boolean onPreferenceChange(Preference preference, Object objValue) {
-        if (mIsCombiningRestrictedAndUnknownOption
-                && preference.getKey().equals(BLOCK_UNKNOWN_NUMBERS_KEY)) {
-            Log.i(this, "onPreferenceChange: changing %s and %s to %b",
-                    preference.getKey(), BLOCK_RESTRICTED_NUMBERS_KEY, (boolean) objValue);
-            BlockedNumbersUtil.setEnhancedBlockSetting(getActivity(), BLOCK_RESTRICTED_NUMBERS_KEY,
-                    (boolean) objValue);
-            BlockedNumbersUtil.setEnhancedBlockSetting(getActivity(),
-                    BLOCK_UNAVAILABLE_NUMBERS_KEY, (boolean) objValue);
+        if (preference.getKey().equals(BLOCK_UNKNOWN_NUMBERS_KEY)) {
+            if (mIsCombiningRestrictedAndUnknownOption) {
+                Log.i(this, "onPreferenceChange: changing %s and %s to %b",
+                        preference.getKey(), BLOCK_RESTRICTED_NUMBERS_KEY, (boolean) objValue);
+                BlockedNumbersUtil.setEnhancedBlockSetting(getActivity(),
+                        BLOCK_RESTRICTED_NUMBERS_KEY, (boolean) objValue);
+            }
+
+            if (mIsCombiningUnavailableAndUnknownOption) {
+                Log.i(this, "onPreferenceChange: changing %s and %s to %b",
+                        preference.getKey(), BLOCK_UNAVAILABLE_NUMBERS_KEY, (boolean) objValue);
+                BlockedNumbersUtil.setEnhancedBlockSetting(getActivity(),
+                        BLOCK_UNAVAILABLE_NUMBERS_KEY, (boolean) objValue);
+            }
         }
         BlockedNumbersUtil.setEnhancedBlockSetting(getActivity(), preference.getKey(),
                 (boolean) objValue);
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/CallEventCallbackAckTransaction.java b/src/com/android/server/telecom/voip/CallEventCallbackAckTransaction.java
new file mode 100644
index 0000000..8b4ffed
--- /dev/null
+++ b/src/com/android/server/telecom/voip/CallEventCallbackAckTransaction.java
@@ -0,0 +1,142 @@
+/*
+ * 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.CallAttributes;
+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 = CallAttributes.AUDIO_CALL;
+    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/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/MaybeHoldCallForNewCallTransaction.java b/src/com/android/server/telecom/voip/MaybeHoldCallForNewCallTransaction.java
new file mode 100644
index 0000000..a245c1c
--- /dev/null
+++ b/src/com/android/server/telecom/voip/MaybeHoldCallForNewCallTransaction.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 MaybeHoldCallForNewCallTransaction extends VoipCallTransaction {
+
+    private static final String TAG = MaybeHoldCallForNewCallTransaction.class.getSimpleName();
+    private final CallsManager mCallsManager;
+    private final Call mCall;
+
+    public MaybeHoldCallForNewCallTransaction(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/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/RequestNewActiveCallTransaction.java b/src/com/android/server/telecom/voip/RequestNewActiveCallTransaction.java
new file mode 100644
index 0000000..f586cc3
--- /dev/null
+++ b/src/com/android/server/telecom/voip/RequestNewActiveCallTransaction.java
@@ -0,0 +1,105 @@
+/*
+ * 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.CallAttributes;
+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 com.android.server.telecom.ConnectionServiceFocusManager;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+
+/**
+ * This transaction should be created when a requesting call would like to go from a valid inactive
+ * state (ex. HELD, RINGING, DIALING) to ACTIVE.
+ *
+ * This class performs some pre-checks to spot a failure in requesting a new call focus and sends
+ * the official request to transition the requested call to ACTIVE.
+ *
+ * Note:
+ * - This Transaction is used for CallControl and CallEventCallbacks, do not put logic in the
+ * onResult/onError that pertains to one direction.
+ * - MaybeHoldCallForNewCallTransaction was performed before this so any potential active calls
+ * should be held now.
+ */
+public class RequestNewActiveCallTransaction extends VoipCallTransaction {
+
+    private static final String TAG = RequestNewActiveCallTransaction.class.getSimpleName();
+    private final CallsManager mCallsManager;
+    private final Call mCall;
+
+    public RequestNewActiveCallTransaction(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<>();
+        int currentCallState = mCall.getState();
+
+        // certain calls cannot go active/answered (ex. disconnect calls, etc.)
+        if (!canBecomeNewCallFocus(currentCallState)) {
+            future.complete(new VoipCallTransactionResult(
+                    CallException.CODE_CALL_CANNOT_BE_SET_TO_ACTIVE,
+                    "CallState cannot be set to active or answered due to current call"
+                            + " state being in invalid state"));
+            return future;
+        }
+
+        if (mCallsManager.getActiveCall() != null) {
+            future.complete(new VoipCallTransactionResult(
+                    CallException.CODE_CALL_CANNOT_BE_SET_TO_ACTIVE,
+                    "Already an active call. Request hold on current active call."));
+            return future;
+        }
+
+        mCallsManager.requestNewCallFocusAndVerify(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;
+    }
+
+    private boolean isPriorityCallingState(int currentCallState) {
+        return ConnectionServiceFocusManager.PRIORITY_FOCUS_CALL_STATE.contains(currentCallState);
+    }
+
+    private boolean canBecomeNewCallFocus(int currentCallState) {
+        return isPriorityCallingState(currentCallState) || currentCallState == CallState.ON_HOLD;
+    }
+}
\ 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..98faf3d
--- /dev/null
+++ b/src/com/android/server/telecom/voip/TransactionManager.java
@@ -0,0 +1,124 @@
+/*
+ * 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.CallException.CODE_OPERATION_TIMED_OUT;
+
+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.onError(new CallException(transactionName + " timeout",
+                            CODE_OPERATION_TIMED_OUT));
+                    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..84fdb5d
--- /dev/null
+++ b/src/com/android/server/telecom/voip/VoipCallMonitor.java
@@ -0,0 +1,282 @@
+/*
+ * 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;
+    // Same notification may be passed as different object in onNotificationPosted and
+    // onNotificationRemoved. Use its string as key to cache ongoing notifications.
+    private final Map<String, 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;
+    private List<StatusBarNotification> mPendingSBN;
+
+    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<>();
+        mPendingSBN = 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 (sbn.getNotification().isStyle(Notification.CallStyle.class)) {
+                        boolean sbnMatched = false;
+                        for (Call call : mPendingCalls) {
+                            if (notificationMatchedCall(sbn, call)) {
+                                mPendingCalls.remove(call);
+                                mNotifications.put(sbn.toString(), call);
+                                sbnMatched = true;
+                                break;
+                            }
+                        }
+                        if (!sbnMatched) {
+                            // notification may posted before we started to monitor the call, cache
+                            // this notification and try to match it later with new added call.
+                            mPendingSBN.add(sbn);
+                        }
+                    }
+                }
+            }
+
+            @Override
+            public void onNotificationRemoved(StatusBarNotification sbn) {
+                synchronized (mLock) {
+                    mPendingSBN.remove(sbn);
+                    if (mNotifications.isEmpty()) {
+                        return;
+                    }
+                    Call call = mNotifications.getOrDefault(sbn.toString(), null);
+                    if (call != null) {
+                        mNotifications.remove(sbn.toString(), 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) {
+        synchronized (mLock) {
+            boolean sbnMatched = false;
+            for (StatusBarNotification sbn : mPendingSBN) {
+                if (notificationMatchedCall(sbn, call)) {
+                    mPendingSBN.remove(sbn);
+                    mNotifications.put(sbn.toString(), call);
+                    sbnMatched = true;
+                    break;
+                }
+            }
+            if (!sbnMatched) {
+                // Only continue to
+                mPendingCalls.add(call);
+                mHandler.postDelayed(() -> {
+                    synchronized (mLock) {
+                        if (mPendingCalls.contains(call)) {
+                            Log.i(this, "Notification for voip-call %s haven't "
+                                    + "posted in time, stop delegation.", call.getId());
+                            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;
+    }
+
+    private boolean notificationMatchedCall(StatusBarNotification sbn, Call call) {
+        String packageName = sbn.getPackageName();
+        UserHandle userHandle = sbn.getUser();
+        PhoneAccountHandle accountHandle = call.getTargetPhoneAccount();
+
+        return packageName != null &&
+                packageName.equals(call.getTargetPhoneAccount()
+                        .getComponentName().getPackageName())
+                && userHandle != null
+                && userHandle.equals(accountHandle.getUserHandle());
+    }
+}
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/BluetoothDeviceManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
index ce1e221..c37d136 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.telecom.tests;
 
+import static android.media.AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHeadset;
@@ -42,7 +44,9 @@
 import org.mockito.Mock;
 
 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.isNull;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Matchers.any;
@@ -59,12 +63,15 @@
 
 @RunWith(JUnit4.class)
 public class BluetoothDeviceManagerTest extends TelecomTestCase {
+    private static final String DEVICE_ADDRESS_1 = "00:00:00:00:00:01";
+
     @Mock BluetoothRouteManager mRouteManager;
     @Mock BluetoothHeadset mBluetoothHeadset;
     @Mock BluetoothAdapter mAdapter;
     @Mock BluetoothHearingAid mBluetoothHearingAid;
     @Mock BluetoothLeAudio mBluetoothLeAudio;
     @Mock AudioManager mockAudioManager;
+    @Mock AudioDeviceInfo mSpeakerInfo;
 
     BluetoothDeviceManager mBluetoothDeviceManager;
     BluetoothProfile.ServiceListener serviceListenerUnderTest;
@@ -116,6 +123,8 @@
                          ArgumentCaptor.forClass(BluetoothLeAudio.Callback.class);
         mBluetoothDeviceManager.setLeAudioServiceForTesting(mBluetoothLeAudio);
         verify(mBluetoothLeAudio).registerCallback(any(), leAudioCallbacksTest.capture());
+
+        when(mSpeakerInfo.getType()).thenReturn(TYPE_BUILTIN_SPEAKER);
     }
 
     @Override
@@ -380,7 +389,7 @@
                         BluetoothDeviceManager.DEVICE_TYPE_HEADSET));
         when(mAdapter.setActiveDevice(nullable(BluetoothDevice.class),
                     eq(BluetoothAdapter.ACTIVE_DEVICE_ALL))).thenReturn(true);
-        mBluetoothDeviceManager.connectAudio(device1.getAddress());
+        mBluetoothDeviceManager.connectAudio(device1.getAddress(), false);
         verify(mAdapter).setActiveDevice(device1, BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL);
         verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class),
                 eq(BluetoothAdapter.ACTIVE_DEVICE_ALL));
@@ -409,12 +418,15 @@
         when(mockAudioManager.setCommunicationDevice(eq(mockAudioDeviceInfo)))
                 .thenReturn(true);
 
-        mBluetoothDeviceManager.connectAudio(device5.getAddress());
+        mBluetoothDeviceManager.connectAudio(device5.getAddress(), false);
         verify(mAdapter).setActiveDevice(device5, BluetoothAdapter.ACTIVE_DEVICE_ALL);
         verify(mBluetoothHeadset, never()).connectAudio();
         verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class),
                 eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL));
 
+        receiverUnderTest.onReceive(mContext, buildActiveDeviceChangeActionIntent(device5,
+                BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID));
+
         when(mockAudioManager.getCommunicationDevice()).thenReturn(mockAudioDeviceInfo);
         mBluetoothDeviceManager.disconnectAudio();
         verify(mockAudioManager).clearCommunicationDevice();
@@ -441,12 +453,15 @@
         when(mockAudioManager.setCommunicationDevice(mockAudioDeviceInfo))
                        .thenReturn(true);
 
-        mBluetoothDeviceManager.connectAudio(device5.getAddress());
+        mBluetoothDeviceManager.connectAudio(device5.getAddress(), false);
         verify(mAdapter).setActiveDevice(device5, BluetoothAdapter.ACTIVE_DEVICE_ALL);
         verify(mBluetoothHeadset, never()).connectAudio();
         verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class),
                 eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL));
 
+        receiverUnderTest.onReceive(mContext, buildActiveDeviceChangeActionIntent(device5,
+                BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
+
         mBluetoothDeviceManager.disconnectAudio();
         verify(mockAudioManager).clearCommunicationDevice();
     }
@@ -465,7 +480,7 @@
         leAudioCallbacksTest.getValue().onGroupNodeAdded(device6, 1);
         when(mAdapter.setActiveDevice(nullable(BluetoothDevice.class),
                 eq(BluetoothAdapter.ACTIVE_DEVICE_ALL))).thenReturn(true);
-        mBluetoothDeviceManager.connectAudio(device5.getAddress());
+        mBluetoothDeviceManager.connectAudio(device5.getAddress(), false);
         verify(mAdapter).setActiveDevice(device5, BluetoothAdapter.ACTIVE_DEVICE_ALL);
         verify(mBluetoothHeadset, never()).connectAudio();
         verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class),
@@ -478,10 +493,54 @@
                 buildConnectionActionIntent(BluetoothHeadset.STATE_DISCONNECTED, device5,
                         BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
 
-        mBluetoothDeviceManager.connectAudio(device6.getAddress());
+        mBluetoothDeviceManager.connectAudio(device6.getAddress(), false);
         verify(mAdapter).setActiveDevice(device6, BluetoothAdapter.ACTIVE_DEVICE_ALL);
     }
 
+    @SmallTest
+    @Test
+    public void testClearHearingAidCommunicationDevice() {
+        AudioDeviceInfo mockAudioDeviceInfo = mock(AudioDeviceInfo.class);
+        when(mockAudioDeviceInfo.getAddress()).thenReturn(DEVICE_ADDRESS_1);
+        when(mockAudioDeviceInfo.getType()).thenReturn(AudioDeviceInfo.TYPE_HEARING_AID);
+        List<AudioDeviceInfo> devices = new ArrayList<>();
+        devices.add(mockAudioDeviceInfo);
+
+        when(mockAudioManager.getAvailableCommunicationDevices())
+                .thenReturn(devices);
+        when(mockAudioManager.setCommunicationDevice(eq(mockAudioDeviceInfo)))
+                .thenReturn(true);
+
+        mBluetoothDeviceManager.setHearingAidCommunicationDevice();
+        when(mockAudioManager.getCommunicationDevice()).thenReturn(mSpeakerInfo);
+        mBluetoothDeviceManager.clearHearingAidCommunicationDevice();
+        verify(mRouteManager).onAudioLost(eq(DEVICE_ADDRESS_1));
+        assertFalse(mBluetoothDeviceManager.isHearingAidSetAsCommunicationDevice());
+    }
+
+    @SmallTest
+    @Test
+    public void testInBandRingingEnabledForLeDevice() {
+        when(mBluetoothHeadset.isInbandRingingEnabled()).thenReturn(false);
+        when(mBluetoothLeAudio.isInbandRingtoneEnabled(1)).thenReturn(true);
+        when(mBluetoothLeAudio.getGroupId(eq(device3))).thenReturn(1);
+        receiverUnderTest.onReceive(mContext,
+                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1,
+                        BluetoothDeviceManager.DEVICE_TYPE_HEADSET));
+        receiverUnderTest.onReceive(mContext,
+                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2,
+                        BluetoothDeviceManager.DEVICE_TYPE_HEADSET));
+        receiverUnderTest.onReceive(mContext,
+                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device3,
+                        BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
+        leAudioCallbacksTest.getValue().onGroupNodeAdded(device3, 1);
+        when(mBluetoothLeAudio.getConnectedGroupLeadDevice(1)).thenReturn(device3);
+        when(mRouteManager.getBluetoothAudioConnectedDevice()).thenReturn(device3);
+        when(mRouteManager.isCachedLeAudioDevice(eq(device3))).thenReturn(true);
+        assertEquals(3, mBluetoothDeviceManager.getNumConnectedDevices());
+        assertTrue(mBluetoothDeviceManager.isInbandRingingEnabled());
+    }
+
     private Intent buildConnectionActionIntent(int state, BluetoothDevice device, int deviceType) {
         String intentString;
 
@@ -505,6 +564,31 @@
         return i;
     }
 
+
+    private Intent buildActiveDeviceChangeActionIntent(BluetoothDevice device, int deviceType) {
+        String intentString;
+
+        switch (deviceType) {
+            case BluetoothDeviceManager.DEVICE_TYPE_HEADSET:
+                intentString = BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED;
+                break;
+            case BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID:
+                intentString = BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED;
+                break;
+            case BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO:
+                intentString = BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED;
+                break;
+            default:
+                return null;
+        }
+
+        Intent i = new Intent(intentString);
+        i.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
+        i.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT
+                | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+        return i;
+    }
+
     private BluetoothDevice makeBluetoothDevice(String address) {
         Parcel p1 = Parcel.obtain();
         p1.writeString(address);
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
index e01cbbe..1a6fb88 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
@@ -48,6 +48,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.reset;
@@ -245,6 +246,7 @@
     }
 
     private void verifyConnectionAttempt(BluetoothDevice device, int numTimes) {
-        verify(mDeviceManager, times(numTimes)).connectAudio(device.getAddress());
+        verify(mDeviceManager, times(numTimes)).connectAudio(eq(device.getAddress()),
+            anyBoolean());
     }
 }
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java b/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
index b729f35..5eecccc 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
@@ -359,18 +359,22 @@
 
         switch (mParams.expectedBluetoothInteraction) {
             case NONE:
-                verify(mDeviceManager, never()).connectAudio(nullable(String.class));
+                verify(mDeviceManager, never()).connectAudio(nullable(String.class),
+                    any(boolean.class));
                 break;
             case CONNECT:
-                verify(mDeviceManager).connectAudio(mParams.expectedConnectionDevice.getAddress());
+                verify(mDeviceManager).connectAudio(mParams.expectedConnectionDevice.getAddress(),
+                    false);
                 verify(mDeviceManager, never()).disconnectAudio();
                 break;
             case CONNECT_SWITCH_DEVICE:
                 verify(mDeviceManager).disconnectAudio();
-                verify(mDeviceManager).connectAudio(mParams.expectedConnectionDevice.getAddress());
+                verify(mDeviceManager).connectAudio(mParams.expectedConnectionDevice.getAddress(),
+                    true);
             break;
             case DISCONNECT:
-                verify(mDeviceManager, never()).connectAudio(nullable(String.class));
+                verify(mDeviceManager, never()).connectAudio(nullable(String.class),
+                    any(boolean.class));
                 verify(mDeviceManager).disconnectAudio();
                 break;
         }
@@ -402,7 +406,8 @@
         when(mDeviceManager.getBluetoothHeadset()).thenReturn(mBluetoothHeadset);
         when(mDeviceManager.getBluetoothHearingAid()).thenReturn(mBluetoothHearingAid);
         when(mDeviceManager.getLeAudioService()).thenReturn(mBluetoothLeAudio);
-        when(mDeviceManager.connectAudio(nullable(String.class))).thenReturn(true);
+        when(mDeviceManager.connectAudio(nullable(String.class), any(boolean.class)))
+            .thenReturn(true);
         when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis(
                 nullable(ContentResolver.class))).thenReturn(100000L);
         when(mTimeoutsAdapter.getBluetoothPendingTimeoutMillis(
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 9ff9986..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,
@@ -395,8 +402,9 @@
                 .forClass(ServiceConnection.class);
         verify(mContext, timeout(CallScreeningServiceFilter.CALL_SCREENING_FILTER_TIMEOUT))
                 .bindServiceAsUser(intentCaptor.capture(), serviceCaptor.capture(),
-                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE),
-                eq(UserHandle.CURRENT));
+                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
+                        | Context.BIND_SCHEDULE_LIKE_TOP_APP),
+                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 e7d0a26..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
@@ -167,11 +192,11 @@
     private static final Uri TEST_ADDRESS = Uri.parse("tel:555-1212");
     private static final Uri TEST_ADDRESS2 = Uri.parse("tel:555-1213");
     private static final Uri TEST_ADDRESS3 = Uri.parse("tel:555-1214");
-    private static final Map<Uri, PhoneAccountHandle> CONTACT_PREFERRED_ACCOUNT =
-            new HashMap<Uri, PhoneAccountHandle>() {{
-                put(TEST_ADDRESS2, SIM_1_HANDLE);
-                put(TEST_ADDRESS3, SIM_2_HANDLE);
-    }};
+    private static final Map<Uri, PhoneAccountHandle> CONTACT_PREFERRED_ACCOUNT = Map.of(
+            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() { };
@@ -201,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;
@@ -211,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;
 
@@ -227,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());
@@ -268,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);
@@ -293,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.
@@ -303,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(
@@ -342,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);
@@ -385,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(
@@ -409,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(
@@ -433,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 */)
@@ -457,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 */)
@@ -483,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(
@@ -504,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(
@@ -599,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() {
@@ -944,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();
@@ -1143,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);
@@ -1171,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);
@@ -1192,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);
@@ -1255,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.
@@ -1401,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)
@@ -1479,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
@@ -1606,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
@@ -1647,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);
@@ -1678,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() {
@@ -1695,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);
     }
@@ -1782,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 639ac22..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;
             }
@@ -260,8 +276,16 @@
                 return Context.PERMISSION_CHECKER_SERVICE;
             } else if (svcClass == SensorPrivacyManager.class) {
                 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
@@ -290,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
@@ -329,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;
         }
 
@@ -532,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
@@ -549,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);
@@ -569,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);
 
@@ -761,6 +806,10 @@
         return mTelephonyManager;
     }
 
+    public AudioManager getAudioManager() {
+        return mAudioManager;
+    }
+
     public CarrierConfigManager getCarrierConfigManager() {
         return mCarrierConfigManager;
     }
@@ -769,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 ddacf43..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;
@@ -181,12 +191,17 @@
     private SystemStateHelper.SystemStateListener mSystemStateListener;
     private CarModeTracker mCarModeTracker = spy(new CarModeTracker());
 
+    private final int serviceBindingFlags = Context.BIND_AUTO_CREATE
+        | Context.BIND_FOREGROUND_SERVICE | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS
+        | Context.BIND_SCHEDULE_LIKE_TOP_APP;
+
     @Override
     @Before
     public void setUp() throws Exception {
         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(
@@ -281,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);
@@ -344,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);
 
@@ -354,9 +379,8 @@
         verify(mMockContext).bindServiceAsUser(
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
-                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
-                        | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
-                eq(UserHandle.CURRENT));
+                eq(serviceBindingFlags),
+                eq(mUserHandle));
 
         Intent bindIntent = bindIntentCaptor.getValue();
         assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
@@ -389,9 +413,8 @@
         verify(mMockContext).bindServiceAsUser(
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
-                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
-                        | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
-                eq(UserHandle.CURRENT));
+                eq(serviceBindingFlags),
+                eq(mUserHandle));
 
         Intent bindIntent = bindIntentCaptor.getValue();
         assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
@@ -419,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);
@@ -442,9 +465,8 @@
         verify(mMockContext, times(1)).bindServiceAsUser(
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
-                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
-                        | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
-                eq(UserHandle.CURRENT));
+                eq(serviceBindingFlags),
+                eq(mUserHandle));
 
         Intent bindIntent = bindIntentCaptor.getValue();
         assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
@@ -466,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);
@@ -473,9 +498,8 @@
         when(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID))
                 .thenReturn(DEF_PKG);
         when(mMockContext.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class),
-                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
-                        | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
-                eq(UserHandle.CURRENT))).thenReturn(true);
+                eq(serviceBindingFlags),
+                eq(mUserHandle))).thenReturn(true);
         when(mTimeoutsAdapter.getEmergencyCallbackWindowMillis(any(ContentResolver.class)))
                 .thenReturn(300_000L);
 
@@ -502,9 +526,8 @@
         verify(mMockContext, times(1)).bindServiceAsUser(
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
-                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
-                        | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
-                eq(UserHandle.CURRENT));
+                eq(serviceBindingFlags),
+                eq(mUserHandle));
 
         Intent bindIntent = bindIntentCaptor.getValue();
         assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
@@ -527,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
@@ -543,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,9 +632,8 @@
         ArgumentCaptor<ServiceConnection> serviceConnectionCaptor =
                 ArgumentCaptor.forClass(ServiceConnection.class);
         when(mMockContext.bindServiceAsUser(any(Intent.class), serviceConnectionCaptor.capture(),
-                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
-                        | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
-                eq(UserHandle.CURRENT))).thenReturn(true);
+                eq(serviceBindingFlags),
+                eq(mUserHandle))).thenReturn(true);
         when(mTimeoutsAdapter.getEmergencyCallbackWindowMillis(any(ContentResolver.class)))
                 .thenReturn(300_000L);
 
@@ -581,9 +660,8 @@
         verify(mMockContext, times(1)).bindServiceAsUser(
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
-                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
-                        | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
-                eq(UserHandle.CURRENT));
+                eq(serviceBindingFlags),
+                eq(mUserHandle));
 
         Intent bindIntent = bindIntentCaptor.getValue();
         assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
@@ -611,9 +689,8 @@
         verify(mMockContext, times(2)).bindServiceAsUser(
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
-                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
-                        | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
-                eq(UserHandle.CURRENT));
+                eq(serviceBindingFlags),
+                eq(mUserHandle));
 
         // Verify we were re-granted the runtime permission.
         verify(mMockPackageManager, times(2)).grantRuntimePermission(eq(SYS_PKG),
@@ -666,9 +743,8 @@
         verify(mMockContext, times(1)).bindServiceAsUser(
                 bindIntentCaptor.capture(),
                 serviceConnectionCaptor.capture(),
-                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
-                        | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
-                eq(UserHandle.CURRENT));
+                eq(serviceBindingFlags),
+                eq(mUserHandle));
 
         Intent bindIntent = bindIntentCaptor.getValue();
         assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
@@ -699,9 +775,8 @@
         verify(mMockContext, times(2)).bindServiceAsUser(
                 bindIntentCaptor2.capture(),
                 any(ServiceConnection.class),
-                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
-                        | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
-                eq(UserHandle.CURRENT));
+                eq(serviceBindingFlags),
+                eq(mUserHandle));
 
         bindIntent = bindIntentCaptor2.getValue();
         assertEquals(SYS_PKG, bindIntent.getComponent().getPackageName());
@@ -716,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(
@@ -747,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));
 
@@ -756,12 +833,51 @@
         verify(mMockContext, times(2)).bindServiceAsUser(
                 bindIntentCaptor2.capture(),
                 any(ServiceConnection.class),
-                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
-                        | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
-                eq(UserHandle.CURRENT));
+                eq(serviceBindingFlags),
+                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.
@@ -791,9 +907,8 @@
         verify(mMockContext, times(1)).bindServiceAsUser(
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
-                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
-                        | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
-                eq(UserHandle.CURRENT));
+                eq(serviceBindingFlags),
+                eq(mUserHandle));
 
         Intent bindIntent = bindIntentCaptor.getValue();
         assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
@@ -813,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)))
@@ -830,9 +946,8 @@
         verify(mMockContext, times(1)).bindServiceAsUser(
                 bindIntentCaptor.capture(),
                 serviceConnectionCaptor.capture(),
-                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
-                        | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
-                eq(UserHandle.CURRENT));
+                eq(serviceBindingFlags),
+                eq(mUserHandle));
 
         // Pretend that the call has gone away.
         when(mMockCallsManager.getCalls()).thenReturn(Collections.emptyList());
@@ -879,9 +994,8 @@
         verify(mMockContext, times(1)).bindServiceAsUser(
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
-                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
-                        | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
-                eq(UserHandle.CURRENT));
+                eq(serviceBindingFlags),
+                eq(mUserHandle));
         // Verify bind car mode ui
         assertEquals(1, bindIntentCaptor.getAllValues().size());
         verifyBinding(bindIntentCaptor, 0, CAR_PKG, CAR_CLASS);
@@ -908,9 +1022,8 @@
         verify(mMockContext, times(1)).bindServiceAsUser(
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
-                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
-                        | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
-                eq(UserHandle.CURRENT));
+                eq(serviceBindingFlags),
+                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);
@@ -952,9 +1065,8 @@
             verify(mMockContext, times(2)).bindServiceAsUser(
                     bindIntentCaptor.capture(),
                     any(ServiceConnection.class),
-                    eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
-                            | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
-                    eq(UserHandle.CURRENT));
+                    eq(serviceBindingFlags),
+                    eq(mUserHandle));
 
             // Verify bind
             assertEquals(2, bindIntentCaptor.getAllValues().size());
@@ -997,9 +1109,8 @@
         verify(mMockContext, times(1)).bindServiceAsUser(
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
-                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
-                        | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
-                eq(UserHandle.CURRENT));
+                eq(serviceBindingFlags),
+                eq(mUserHandle));
 
         // Verify bind
         assertEquals(1, bindIntentCaptor.getAllValues().size());
@@ -1008,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
@@ -1031,9 +1144,8 @@
         verify(mMockContext, times(1)).bindServiceAsUser(
                 bindIntentCaptor.capture(),
                 serviceConnectionCaptor.capture(),
-                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
-                        | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
-                eq(UserHandle.CURRENT));
+                eq(serviceBindingFlags),
+                eq(mUserHandle));
         assertEquals(1, bindIntentCaptor.getAllValues().size());
         verifyBinding(bindIntentCaptor, 0, DEF_PKG, DEF_CLASS);
 
@@ -1070,9 +1182,8 @@
         verify(mMockContext).bindServiceAsUser(
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
-                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
-                        | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
-                eq(UserHandle.CURRENT));
+                eq(serviceBindingFlags),
+                eq(mUserHandle));
     }
 
     /**
@@ -1102,9 +1213,8 @@
         verify(mMockContext, times(4)).bindServiceAsUser(
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
-                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
-                        | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
-                eq(UserHandle.CURRENT));
+                eq(serviceBindingFlags),
+                eq(mUserHandle));
         // Verify bind car mode ui
         assertEquals(4, bindIntentCaptor.getAllValues().size());
 
@@ -1141,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)))
@@ -1160,9 +1271,8 @@
         verify(mMockContext, times(2)).bindServiceAsUser(
                 bindIntentCaptor.capture(),
                 serviceConnectionCaptor.capture(),
-                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
-                        | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
-                eq(UserHandle.CURRENT));
+                eq(serviceBindingFlags),
+                eq(mUserHandle));
 
         CompletableFuture<Boolean> bindTimeout = mInCallController.getBindingFuture();
 
@@ -1229,9 +1339,8 @@
         verify(mMockContext, never()).bindServiceAsUser(
                 any(Intent.class),
                 any(ServiceConnection.class),
-                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
-                        | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
-                eq(UserHandle.CURRENT));
+                eq(serviceBindingFlags),
+                eq(mUserHandle));
 
         // Now switch to car mode.
         // Enable car mode and enter car mode at default priority.
@@ -1242,9 +1351,8 @@
         verify(mMockContext, times(1)).bindServiceAsUser(
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
-                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
-                        | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
-                eq(UserHandle.CURRENT));
+                eq(serviceBindingFlags),
+                eq(mUserHandle));
         // Verify bind car mode ui
         assertEquals(1, bindIntentCaptor.getAllValues().size());
         verifyBinding(bindIntentCaptor, 0, CAR_PKG, CAR_CLASS);
@@ -1269,9 +1377,8 @@
         verify(mMockContext, never()).bindServiceAsUser(
                 any(Intent.class),
                 any(ServiceConnection.class),
-                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
-                        | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
-                eq(UserHandle.CURRENT));
+                eq(serviceBindingFlags),
+                eq(mUserHandle));
 
         // Now switch to car mode.
         // Enable car mode and enter car mode at default priority.
@@ -1287,9 +1394,8 @@
         verify(mMockContext, times(1)).bindServiceAsUser(
                 any(Intent.class),
                 serviceConnectionCaptor.capture(),
-                eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
-                        | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS),
-                eq(UserHandle.CURRENT));
+                eq(serviceBindingFlags),
+                eq(mUserHandle));
 
         ServiceConnection serviceConnection = serviceConnectionCaptor.getValue();
         ComponentName defDialerComponentName = new ComponentName(DEF_PKG, DEF_CLASS);
@@ -1305,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 */);
     }
@@ -1317,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);
@@ -1508,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/ProximitySensorManagerTest.java b/tests/src/com/android/server/telecom/tests/ProximitySensorManagerTest.java
index eafaa53..807b7cf 100644
--- a/tests/src/com/android/server/telecom/tests/ProximitySensorManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/ProximitySensorManagerTest.java
@@ -31,7 +31,7 @@
 import org.junit.runners.JUnit4;
 import org.mockito.Mock;
 
-import java.util.ArrayList;
+import java.util.List;
 
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -67,9 +67,7 @@
     @SmallTest
     @Test
     public void testTurnOnProximityWithCallsActive() throws Exception {
-        when(mCallsManager.getCalls()).thenReturn(new ArrayList<Call>(){{
-            add(mCall);
-        }});
+        when(mCallsManager.getCalls()).thenReturn(List.of(mCall));
         when(mWakeLockAdapter.isHeld()).thenReturn(false);
 
         mProximitySensorManager.turnOn();
@@ -80,7 +78,7 @@
     @SmallTest
     @Test
     public void testTurnOnProximityWithNoCallsActive() throws Exception {
-        when(mCallsManager.getCalls()).thenReturn(new ArrayList<Call>());
+        when(mCallsManager.getCalls()).thenReturn(List.of());
         when(mWakeLockAdapter.isHeld()).thenReturn(false);
 
         mProximitySensorManager.turnOn();
@@ -102,9 +100,7 @@
     @SmallTest
     @Test
     public void testCallRemovedFromCallsManagerCallsActive() throws Exception {
-        when(mCallsManager.getCalls()).thenReturn(new ArrayList<Call>(){{
-            add(mCall);
-        }});
+        when(mCallsManager.getCalls()).thenReturn(List.of(mCall));
         when(mWakeLockAdapter.isHeld()).thenReturn(true);
 
         mProximitySensorManager.onCallRemoved(mock(Call.class));
@@ -115,7 +111,7 @@
     @SmallTest
     @Test
     public void testCallRemovedFromCallsManagerNoCallsActive() throws Exception {
-        when(mCallsManager.getCalls()).thenReturn(new ArrayList<Call>());
+        when(mCallsManager.getCalls()).thenReturn(List.of());
         when(mWakeLockAdapter.isHeld()).thenReturn(true);
 
         mProximitySensorManager.onCallRemoved(mock(Call.class));
diff --git a/tests/src/com/android/server/telecom/tests/RingerTest.java b/tests/src/com/android/server/telecom/tests/RingerTest.java
index 59a5f02..cf5b791 100644
--- a/tests/src/com/android/server/telecom/tests/RingerTest.java
+++ b/tests/src/com/android/server/telecom/tests/RingerTest.java
@@ -16,7 +16,28 @@
 
 package com.android.server.telecom.tests;
 
+import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.atLeastOnce;
+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;
@@ -24,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;
 
@@ -48,83 +71,39 @@
 import org.mockito.Mock;
 import org.mockito.Spy;
 
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.ArgumentMatchers.nullable;
-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.verify;
-import static org.mockito.Mockito.when;
-
-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
@@ -132,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);
-        when(mockAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
-        when(mockSystemSettingsUtil.isHapticPlaybackSupported(any(Context.class))).thenReturn(true);
-        NotificationManager notificationManager =
-                (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+        mockAudioManager = mContext.getSystemService(AudioManager.class);
+        when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
+        when(mockSystemSettingsUtil.isHapticPlaybackSupported(any(Context.class)))
+                .thenAnswer((invocation) -> mIsHapticPlaybackSupported);
+        mockNotificationManager =mContext.getSystemService(NotificationManager.class);
         when(mockTonePlayer.startTone()).thenReturn(true);
-        when(notificationManager.matchesCallFilter(any(Bundle.class))).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);
     }
 
@@ -165,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));
     }
@@ -199,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));
     }
@@ -215,51 +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
-        NotificationManager notificationManager =
-                (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
-        when(notificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(false);
+    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));
     }
@@ -267,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));
     }
@@ -284,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.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
-        mFuture.complete(false); // not using audio coupled haptics
+        when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
         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));
@@ -303,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(mockAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
+        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));
@@ -324,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.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
+        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));
@@ -344,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.getRingerModeInternal()).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);
-        when(mockAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
-        when(mockRingtoneFactory.getRingtone(any(Call.class))).thenReturn(mockRingtone);
+        Ringtone mockRingtone = ensureRingtoneMocked();
+        when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
         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));
     }
@@ -405,63 +388,175 @@
     @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(startRingingAndWaitForAsync(mockCall2, false));
+        verify(mockRingtoneFactory, times(1))
+            .getRingtone(any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean());
+        verifyNoMoreInteractions(mockRingtoneFactory);
+        verify(mockTonePlayer).stopTone();
+        verify(mockRingtone).play();
+    }
+
+    @SmallTest
+    @Test
+    public void testSilentRingWithHfpStillAcquiresFocus() throws Exception {
+        mRingerUnderTest.startCallWaiting(mockCall1);
+        when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
+        when(mockAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(0);
+        enableVibrationOnlyWhenNotRinging();
+        assertTrue(startRingingAndWaitForAsync(mockCall2, true));
+        verify(mockTonePlayer).stopTone();
+        // Ringer not audible, so never tries to create a ringtone.
+        verifyZeroInteractions(mockRingtoneFactory);
+        verify(mockVibrator, never())
+                .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
+    }
+
+    @SmallTest
+    @Test
+    public void testRingAndVibrateForAllowedCallInDndMode() throws Exception {
+        mRingerUnderTest.startCallWaiting(mockCall1);
+        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);
+        enableVibrationWhenRinging();
+        assertTrue(startRingingAndWaitForAsync(mockCall2, true));
+        verify(mockRingtoneFactory, times(1))
+            .getRingtone(any(Call.class), isNull(), anyBoolean());
+        verifyNoMoreInteractions(mockRingtoneFactory);
+        verify(mockTonePlayer).stopTone();
+        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));
-        mRingCompletionFuture.get();
-        verify(mockTonePlayer).stopTone();
-        verify(mockRingtonePlayer).play(
-            any(RingtoneFactory.class), any(Call.class), any(VolumeShaper.Configuration.class),
-                eq(true) /* isRingerAudible */, eq(true) /* isVibrationEnabled */);
+        mRingerUnderTest.stopRinging();
+        verify(mockAccessibilityManagerAdapter, atLeastOnce())
+                .stopFlashNotificationSequence(any(Context.class));
+        mRingCompletionFuture.get();  // Don't leak async work.
     }
 
     @SmallTest
     @Test
-    public void testSilentRingWithHfpStillAcquiresFocus1() throws Exception {
-        mRingerUnderTest.startCallWaiting(mockCall1);
-        Ringtone mockRingtone = mock(Ringtone.class);
-        when(mockRingtoneFactory.getRingtone(any(Call.class))).thenReturn(mockRingtone);
-        when(mockAudioManager.getRingerModeInternal()).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());
+    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));
     }
 
-    @SmallTest
-    @Test
-    public void testSilentRingWithHfpStillAcquiresFocus2() throws Exception {
-        mRingerUnderTest.startCallWaiting(mockCall1);
-        when(mockRingtoneFactory.getRingtone(any(Call.class))).thenReturn(null);
-        when(mockAudioManager.getRingerModeInternal()).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));
+    /**
+     * 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();
-        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));
+        return result;
     }
 
     private void ensureRingerIsAudible() {
-        Ringtone mockRingtone = mock(Ringtone.class);
-        when(mockRingtoneFactory.getRingtone(any(Call.class))).thenReturn(mockRingtone);
-        when(mockAudioManager.getRingerModeInternal()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
+        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);
@@ -475,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/SessionTest.java b/tests/src/com/android/server/telecom/tests/SessionTest.java
index 4be3dad..f38618c 100644
--- a/tests/src/com/android/server/telecom/tests/SessionTest.java
+++ b/tests/src/com/android/server/telecom/tests/SessionTest.java
@@ -144,6 +144,7 @@
      * Ensure creating two sessions that are parent/child of each other does not lead to a crash
      * or infinite recursion when using Session#toString.
      */
+    @SuppressWarnings("ReturnValueIgnored")
     @SmallTest
     @Test
     public void testRecursion_toString() {
@@ -159,7 +160,6 @@
 
         // Make sure calling these methods does not result in a crash
         try {
-
             parentSession.toString();
             childSession.toString();
         } catch (Exception e) {
@@ -176,6 +176,7 @@
      * Ensure creating two sessions and setting the child as the parent to itself doesn't cause a
      * crash due to infinite recursion.
      */
+    @SuppressWarnings("ReturnValueIgnored")
     @SmallTest
     @Test
     public void testRecursion_toString_childCircDep() {
@@ -237,6 +238,7 @@
      * Ensure creating two sessions that are parent/child of each other does not lead to a crash
      * or infinite recursion in the general case.
      */
+    @SuppressWarnings("ReturnValueIgnored")
     @SmallTest
     @Test
     public void testRecursion() {
diff --git a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
index 90bdc80..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,40 +421,106 @@
                 .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
     @Test
     public void testGetCallCapablePhoneAccounts() throws RemoteException {
-        List<PhoneAccountHandle> fullPHList = new ArrayList<PhoneAccountHandle>() {{
-            add(TEL_PA_HANDLE_16);
-            add(SIP_PA_HANDLE_17);
-        }};
+        List<PhoneAccountHandle> fullPHList = List.of(TEL_PA_HANDLE_16, SIP_PA_HANDLE_17);
+        List<PhoneAccountHandle> smallPHList = List.of(SIP_PA_HANDLE_17);
 
-        List<PhoneAccountHandle> smallPHList = new ArrayList<PhoneAccountHandle>() {{
-            add(SIP_PA_HANDLE_17);
-        }};
         // 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,
@@ -400,40 +533,78 @@
 
     @SmallTest
     @Test
-    public void testGetCallCapablePhoneAccountsFailure() throws RemoteException {
-        List<String> enforcedPermissions = new ArrayList<String>() {{
-            add(READ_PHONE_STATE);
-            add(READ_PRIVILEGED_PHONE_STATE);
-        }};
+    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
     @Test
     public void testGetPhoneAccountsSupportingScheme() throws RemoteException {
-        List<PhoneAccountHandle> sipPHList = new ArrayList<PhoneAccountHandle>() {{
-            add(SIP_PA_HANDLE_17);
-        }};
+        List<PhoneAccountHandle> sipPHList = List.of(SIP_PA_HANDLE_17);
+        List<PhoneAccountHandle> telPHList = List.of(TEL_PA_HANDLE_16);
 
-        List<PhoneAccountHandle> telPHList = new ArrayList<PhoneAccountHandle>() {{
-            add(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);
 
@@ -447,13 +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 = new ArrayList<PhoneAccountHandle>() {{
-            add(TEL_PA_HANDLE_16);
-            add(SIP_PA_HANDLE_17);
-        }};
+        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,
@@ -463,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());
@@ -479,15 +671,96 @@
 
     @SmallTest
     @Test
-    public void testGetAllPhoneAccounts() throws RemoteException {
-        List<PhoneAccount> phoneAccountList = new ArrayList<PhoneAccount>() {{
-            add(makePhoneAccount(TEL_PA_HANDLE_16).build());
-            add(makePhoneAccount(SIP_PA_HANDLE_17).build());
-        }};
-        when(mFakePhoneAccountRegistrar.getAllPhoneAccounts(any(UserHandle.class)))
+    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(2, mTSIBinder.getAllPhoneAccounts().getList().size());
+        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), anyBoolean()))
+                .thenReturn(phoneAccountList);
+
+        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
@@ -505,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
@@ -602,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;
         }
@@ -619,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(
@@ -628,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);
     }
 
@@ -645,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
         }
@@ -657,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
         }
@@ -683,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
         }
@@ -706,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
@@ -732,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),
@@ -744,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));
 
@@ -760,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);
@@ -775,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);
@@ -793,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);
@@ -811,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
@@ -1103,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);
@@ -1136,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));
     }
@@ -1178,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));
     }
 
@@ -1200,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));
     }
 
@@ -1228,7 +2010,7 @@
         } catch (SecurityException e) {
             // desired result
         }
-        verify(mFakeCallsManager, never()).hasOngoingCalls();
+        verify(mFakeCallsManager, never()).hasOngoingCalls(any(UserHandle.class), anyBoolean());
     }
 
     /**
@@ -1264,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.
@@ -1288,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");
     }
@@ -1299,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..3fc87a9
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/TransactionTests.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 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.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.MaybeHoldCallForNewCallTransaction;
+import com.android.server.telecom.voip.RequestNewActiveCallTransaction;
+
+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 testRequestNewCallFocusWithDialingCall() throws Exception {
+        // GIVEN
+        RequestNewActiveCallTransaction transaction =
+                new RequestNewActiveCallTransaction(mCallsManager, mMockCall1);
+
+        // WHEN
+        when(mMockCall1.getState()).thenReturn(CallState.DIALING);
+        transaction.processTransaction(null);
+
+        // THEN
+        verify(mCallsManager, times(1))
+                .requestNewCallFocusAndVerify(eq(mMockCall1), isA(OutcomeReceiver.class));
+    }
+
+    @Test
+    public void testRequestNewCallFocusWithRingingCall() throws Exception {
+        // GIVEN
+        RequestNewActiveCallTransaction transaction =
+                new RequestNewActiveCallTransaction(mCallsManager, mMockCall1);
+
+        // WHEN
+        when(mMockCall1.getState()).thenReturn(CallState.RINGING);
+        transaction.processTransaction(null);
+
+        // THEN
+        verify(mCallsManager, times(1))
+                .requestNewCallFocusAndVerify(eq(mMockCall1), isA(OutcomeReceiver.class));
+    }
+
+    @Test
+    public void testRequestNewCallFocusFailure() throws Exception {
+        // GIVEN
+        RequestNewActiveCallTransaction transaction =
+                new RequestNewActiveCallTransaction(mCallsManager, mMockCall1);
+
+        // WHEN
+        when(mMockCall1.getState()).thenReturn(CallState.DISCONNECTING);
+        when(mCallsManager.getActiveCall()).thenReturn(null);
+        transaction.processTransaction(null);
+
+        // THEN
+        verify(mCallsManager, times(0))
+                .requestNewCallFocusAndVerify( eq(mMockCall1), isA(OutcomeReceiver.class));
+    }
+
+    @Test
+    public void testTransactionalHoldActiveCallForNewCall() throws Exception {
+        // GIVEN
+        MaybeHoldCallForNewCallTransaction transaction =
+                new MaybeHoldCallForNewCallTransaction(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..e2c7b7b
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/VoipCallTransactionTest.java
@@ -0,0 +1,253 @@
+/*
+ * 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<>() {
+            @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<String> exceptionFuture = new CompletableFuture<>();
+        OutcomeReceiver<VoipCallTransactionResult, CallException> outcomeReceiver =
+                new OutcomeReceiver<>() {
+                    @Override
+                    public void onResult(VoipCallTransactionResult result) {
+
+                    }
+
+                    @Override
+                    public void onError(CallException e) {
+                        exceptionFuture.complete(e.getMessage());
+                    }
+                };        mTransactionManager.addTransaction(t, outcomeReceiver);
+        String message = exceptionFuture.get(7000L, TimeUnit.MILLISECONDS);
+        assertTrue(message.contains("timeout"));
+    }
+}