Merge Android U (ab/10368041)

Bug: 291102124
Merged-In: I2855ba6f72a495a3ba13aff479b35ab68a6bc7d6
Change-Id: I33b944e43066b20c45e62f3a17a74e964370265f
diff --git a/Android.bp b/Android.bp
index 1b422aa..501b438 100644
--- a/Android.bp
+++ b/Android.bp
@@ -27,6 +27,10 @@
     ],
     static_libs: [
         "androidx.annotation_annotation",
+        "androidx.core_core",
+    ],
+    libs: [
+        "services",
     ],
     resource_dirs: ["res"],
     proto: {
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index c26650c..ab067d9 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -63,7 +63,10 @@
     <uses-permission android:name="android.permission.WRITE_BLOCKED_NUMBERS"/>
     <uses-permission android:name="android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME"/>
     <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
+    <uses-permission android:name="android.permission.USE_COLORIZED_NOTIFICATIONS"/>
+    <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT"/>
     <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 +140,7 @@
              android:permission="android.permission.CALL_PHONE"
              android:excludeFromRecents="true"
              android:process=":ui"
+             android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|density|fontScale|keyboard|layoutDirection|locale|navigation|smallestScreenSize|touchscreen|uiMode"
              android:exported="true">
             <!-- CALL action intent filters for the various ways of initiating an outgoing call. -->
             <intent-filter>
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 9874044..489fab7 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,2 +1,3 @@
-[Hook Scripts]
-aosp_hook = ${REPO_ROOT}/packages/services/Telecomm/scripts/aosp_tag_preupload.py ${PREUPLOAD_COMMIT}
+# Uncomment to re-enable aosp warning.
+#[Hook Scripts]
+#aosp_hook = ${REPO_ROOT}/packages/services/Telecomm/scripts/aosp_tag_preupload.py ${PREUPLOAD_COMMIT}
diff --git a/res/drawable/gm_phonelink.xml b/res/drawable/gm_phonelink.xml
new file mode 100644
index 0000000..2ffba0e
--- /dev/null
+++ b/res/drawable/gm_phonelink.xml
@@ -0,0 +1,11 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="20dp"
+    android:height="20dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24"
+    android:tint="?android:attr/colorControlNormal"
+    android:autoMirrored="true">
+  <path
+      android:fillColor="#FF000000"
+      android:pathData="M5,6h16L21,4L5,4c-1.1,0 -2,0.9 -2,2v11L1,17v3h11v-3L5,17L5,6zM21,8h-6c-0.55,0 -1,0.45 -1,1v10c0,0.55 0.45,1 1,1h6c0.55,0 1,-0.45 1,-1L22,9c0,-0.55 -0.45,-1 -1,-1zM20,17h-4v-7h4v7z"/>
+</vector>
diff --git a/res/drawable/person_circle.xml b/res/drawable/person_circle.xml
new file mode 100644
index 0000000..e139b4f
--- /dev/null
+++ b/res/drawable/person_circle.xml
@@ -0,0 +1,13 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="48dp"
+    android:height="48dp"
+    android:viewportWidth="48"
+    android:viewportHeight="48">
+  <path
+      android:pathData="M21.094,0.92C22.839,-0.307 25.161,-0.307 26.906,0.92C28.031,1.71 29.428,2.009 30.777,1.746C32.868,1.339 34.989,2.287 36.086,4.12C36.794,5.302 37.95,6.145 39.288,6.456C41.363,6.938 42.917,8.671 43.177,10.794C43.345,12.163 44.059,13.406 45.156,14.236C46.856,15.524 47.574,17.742 46.952,19.788C46.551,21.107 46.7,22.534 47.365,23.741C48.397,25.612 48.154,27.931 46.758,29.546C45.857,30.587 45.416,31.951 45.535,33.326C45.72,35.457 44.559,37.476 42.629,38.381C41.384,38.965 40.429,40.03 39.981,41.335C39.287,43.357 37.408,44.727 35.279,44.766C33.906,44.79 32.601,45.374 31.664,46.382C30.211,47.946 27.939,48.431 25.979,47.596C24.714,47.057 23.286,47.057 22.021,47.596C20.061,48.431 17.789,47.946 16.336,46.382C15.399,45.374 14.094,44.79 12.721,44.766C10.592,44.727 8.713,43.357 8.019,41.335C7.571,40.03 6.616,38.965 5.371,38.381C3.441,37.476 2.28,35.457 2.465,33.326C2.584,31.951 2.143,30.587 1.242,29.546C-0.154,27.931 -0.397,25.612 0.635,23.741C1.3,22.534 1.449,21.107 1.048,19.788C0.427,17.742 1.144,15.524 2.844,14.236C3.941,13.406 4.655,12.163 4.823,10.794C5.083,8.671 6.637,6.938 8.712,6.456C10.05,6.145 11.206,5.302 11.914,4.12C13.011,2.287 15.132,1.339 17.223,1.746C18.572,2.009 19.969,1.71 21.094,0.92Z"
+      android:fillColor="#000000"/>
+  <path
+      android:pathData="M24.001,15.467C21.644,15.467 19.734,17.376 19.734,19.733C19.734,22.091 21.644,24 24.001,24C26.358,24 28.268,22.091 28.268,19.733C28.268,17.376 26.358,15.467 24.001,15.467ZM26.134,19.734C26.134,18.56 25.174,17.601 24,17.601C22.827,17.601 21.867,18.56 21.867,19.734C21.867,20.907 22.827,21.867 24,21.867C25.174,21.867 26.134,20.907 26.134,19.734ZM30.402,29.333C30.188,28.576 26.882,27.2 24.002,27.2C21.122,27.2 17.815,28.576 17.602,29.344V30.4H30.402V29.333ZM15.469,29.333C15.469,26.496 21.154,25.066 24.002,25.066C26.85,25.066 32.535,26.496 32.535,29.333V32.533H15.469V29.333Z"
+      android:fillColor="#ffffff"
+      android:fillType="evenOdd"/>
+</vector>
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index a4b41db..61381ae 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -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 3e91608..fc36464 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -90,7 +90,7 @@
     <string name="answering_ends_other_managed_video_call" msgid="1988508241432031327">"መመለስ እየተካሄደ ያለ የቪዲዮ ጥሪዎን ይጨርሳል"</string>
     <string name="answer_incoming_call" msgid="2045888814782215326">"ይመልሱ"</string>
     <string name="decline_incoming_call" msgid="922147089348451310">"አትቀበል"</string>
-    <string name="cant_call_due_to_no_supported_service" msgid="1635626384149947077">"የዚህን ዓይነት ጥሪዎች የሚደግፉ መደወያ መለያዎች ስለሌሉ ጥሪ መደረግ አይችልም።"</string>
+    <string name="cant_call_due_to_no_supported_service" msgid="1635626384149947077">"የዚህን አይነት ጥሪዎች የሚደግፉ መደወያ መለያዎች ስለሌሉ ጥሪ መደረግ አይችልም።"</string>
     <string name="cant_call_due_to_ongoing_call" msgid="8004235328451385493">"በ<xliff:g id="OTHER_CALL">%1$s</xliff:g> ጥሪዎ ምክንያት ጥሪ መደረግ አይችልም።"</string>
     <string name="cant_call_due_to_ongoing_calls" msgid="6379163795277824868">"በ<xliff:g id="OTHER_CALL">%1$s</xliff:g> ጥሪዎችዎ ምክንያት ጥሪዎች መደረግ አይችሉም።"</string>
     <string name="cant_call_due_to_ongoing_unknown_call" msgid="8243532328969433172">"በሌላ መተግበሪያ ውስጥ ባለ ጥሪ ምክንያት ጥሪ መደረግ አይችልም።"</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 fe39603..b9f8842 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -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 004e62e..9226599 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -108,7 +108,7 @@
     <string name="phone_settings_call_blocking_txt" msgid="7311523114822507178">"কল অৱৰোধ"</string>
     <string name="phone_settings_number_not_in_contact_txt" msgid="2602249106007265757">"আপোনাৰ সর্ম্পকসূচীত নথকা"</string>
     <string name="phone_settings_number_not_in_contact_summary_txt" msgid="963327038085718969">"আপোনাৰ সর্ম্পকসূচীত নথকা নম্বৰ অৱৰোধ কৰক"</string>
-    <string name="phone_settings_private_num_txt" msgid="6339272760338475619">"ব্যক্তিগত"</string>
+    <string name="phone_settings_private_num_txt" msgid="6339272760338475619">"ব্য়ক্তিগত"</string>
     <string name="phone_settings_private_num_summary_txt" msgid="6755758240544021037">"যিসকল কল কৰোঁতাই তেওঁলোকৰ নম্বৰ প্ৰকাশ নকৰে তেওঁলোকক অৱৰোধ কৰক"</string>
     <string name="phone_settings_payphone_txt" msgid="5003987966052543965">"পে\'ফ\'ন"</string>
     <string name="phone_settings_payphone_summary_txt" msgid="3936631076065563665">"পে\'ফ\'নৰ পৰা অহা কল অৱৰোধ কৰক"</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 c8e403b..d2368fa 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -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 1a8b1fb..f77b0bb 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -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 8fc4be7..8560c9c 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -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 f67820d..c99dcd0 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -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 800db09..01b67f0 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -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 d8708d1..201d8d1 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -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 45f182c..2c5727d 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -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 9238421..2945d28 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -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 9e755ad..366b584 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -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 659ff91..801321b 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -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 91d94d4..7a09f0a 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -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 52a1c64..0249401 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -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 55df474..5f857c1 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -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 52a1c64..0249401 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -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 52a1c64..0249401 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -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 e5d1332..2ffae87 100644
--- a/res/values-en-rXC/strings.xml
+++ b/res/values-en-rXC/strings.xml
@@ -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 4c6fe21..ab8f454 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -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 1e5866a..65ab627 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -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 46d36bc..7d9ad7b 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -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 d6abf21..64645a4 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -47,7 +47,7 @@
     <string name="respond_via_sms_failure_format" msgid="5198680980054596391">"Ezin izan da bidali mezua <xliff:g id="PHONE_NUMBER">%s</xliff:g> zenbakira."</string>
     <string name="enable_account_preference_title" msgid="6949224486748457976">"Deiak egiteko kontuak"</string>
     <string name="outgoing_call_not_allowed_user_restriction" msgid="3424338207838851646">"Larrialdi-deiak bakarrik egin daitezke."</string>
-    <string name="outgoing_call_not_allowed_no_permission" msgid="8590468836581488679">"Aplikazioak irteerako deiak egin ahal izan ditzan, telefonoaren eginbidea erabiltzeko baimena behar du."</string>
+    <string name="outgoing_call_not_allowed_no_permission" msgid="8590468836581488679">"Aplikazioak deitu ahal izan dezan, telefonoaren eginbidea erabiltzeko baimena behar du."</string>
     <string name="outgoing_call_error_no_phone_number_supplied" msgid="7665135102566099778">"Deitzeko, idatzi balio duen zenbaki bat."</string>
     <string name="duplicate_video_call_not_allowed" msgid="5754746140185781159">"Une honetan ezin da deirik gehitu."</string>
     <string name="no_vm_number" msgid="2179959110602180844">"Erantzungailuaren zenbakia falta da"</string>
@@ -94,7 +94,7 @@
     <string name="cant_call_due_to_ongoing_call" msgid="8004235328451385493">"Ezin da egin deia, beste dei bat abian delako <xliff:g id="OTHER_CALL">%1$s</xliff:g> zerbitzuan."</string>
     <string name="cant_call_due_to_ongoing_calls" msgid="6379163795277824868">"Ezin da egin deia, beste dei batzuk abian direlako <xliff:g id="OTHER_CALL">%1$s</xliff:g> zerbitzuan."</string>
     <string name="cant_call_due_to_ongoing_unknown_call" msgid="8243532328969433172">"Ezin da egin deia, beste dei bat abian delako beste aplikazio batean."</string>
-    <string name="notification_channel_incoming_call" msgid="5245550964701715662">"Sarrerako deiak"</string>
+    <string name="notification_channel_incoming_call" msgid="5245550964701715662">"Jasotako deiak"</string>
     <string name="notification_channel_missed_call" msgid="7168893015283909012">"Dei galduak"</string>
     <string name="notification_channel_call_blocking" msgid="2028807677868598710">"Deiak blokeatzeko aukera"</string>
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"Atzeko planoko deiak"</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 11f5491..83c8034 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -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 30809ed..4ade7d1 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -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 789909a..95b2069 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -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 75882d1..03f6d87 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -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 23fa036..a8443dd 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -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 2b2ad64..4af6351 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -25,7 +25,7 @@
     <string name="notification_missedCallsMsg" msgid="5055782736170916682">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> ચૂકી ગયેલા કૉલ"</string>
     <string name="notification_missedCallTicker" msgid="6731461957487087769">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g> નો કૉલ ચૂકી ગયાં"</string>
     <string name="notification_missedCall_call_back" msgid="7900333283939789732">"કૉલ બેક"</string>
-    <string name="notification_missedCall_message" msgid="4054698824390076431">"મેસેજ"</string>
+    <string name="notification_missedCall_message" msgid="4054698824390076431">"સંદેશ"</string>
     <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"ડિસ્કનેક્ટ કરેલો કૉલ"</string>
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"ઇમર્જન્સી કૉલને કારણે <xliff:g id="CALLER">%s</xliff:g>નો કૉલ ડિસ્કનેક્ટ કરવામાં આવ્યો છે."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"ઇમર્જન્સી કૉલને કારણે તમારો કૉલ ડિસ્કનેક્ટ કરવામાં આવ્યો છે."</string>
@@ -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 e909686..918051a 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -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 7adae61..02c91fb 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -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 b9775e9..cdda34a 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -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 962fd89..d85d037 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -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 c378d37..84c0d39 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -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 7d3b6bb..db7dbeb 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -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 3f78593..ad070d6 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -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 d1d1c70..d557599 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -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 0426975..73b85d9 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -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 3aaa73e..33c5a47 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -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 2836b5d..7c07654 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -75,10 +75,10 @@
     <string name="blocked_numbers_butter_bar_title" msgid="582982373755950791">"Тыйым уақытша алынды"</string>
     <string name="blocked_numbers_butter_bar_body" msgid="1261213114919301485">"Төтенше жағдай нөмірін терген немесе мәтіндік хабар жіберген соң, төтенше жағдай қызметтері сізге хабарласа алуы үшін тыйым алынады."</string>
     <string name="blocked_numbers_butter_bar_button" msgid="2704456308072489793">"Қазір қайта қосу"</string>
-    <string name="blocked_numbers_number_blocked_message" msgid="4314736791180919167">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> блокталған"</string>
+    <string name="blocked_numbers_number_blocked_message" msgid="4314736791180919167">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> бөгелген"</string>
     <string name="blocked_numbers_number_unblocked_message" msgid="2933071624674945601">"<xliff:g id="UNBLOCKED_NUMBER">%1$s</xliff:g> бөгеуден шығарылды"</string>
     <string name="blocked_numbers_block_emergency_number_message" msgid="4198550501500893890">"Жедел қызмет нөмірін бөгеу мүмкін емес."</string>
-    <string name="blocked_numbers_number_already_blocked_message" msgid="2301270825735665458">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> бұрыннан блокталған."</string>
+    <string name="blocked_numbers_number_already_blocked_message" msgid="2301270825735665458">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> бұрыннан бөгелген."</string>
     <string name="toast_personal_call_msg" msgid="5817631570381795610">"Қоңырау шалу үшін жеке нөмір тергішті пайдалану"</string>
     <string name="notification_incoming_call" msgid="1233481138362230894">"<xliff:g id="CALL_VIA">%1$s</xliff:g> қоңырауы: <xliff:g id="CALL_FROM">%2$s</xliff:g>"</string>
     <string name="notification_incoming_video_call" msgid="5795968314037063900">"<xliff:g id="CALL_VIA">%1$s</xliff:g> бейне қоңырауы: <xliff:g id="CALL_FROM">%2$s</xliff:g>"</string>
@@ -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 b00ab2b..64e47ef 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -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 885fc65..8109de2 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -53,7 +53,7 @@
     <string name="no_vm_number" msgid="2179959110602180844">"ಧ್ವನಿಮೇಲ್‌ ಸಂಖ್ಯೆಯು ಕಾಣೆಯಾಗಿದೆ"</string>
     <string name="no_vm_number_msg" msgid="1339245731058529388">"ಸಿಮ್‌ ಕಾರ್ಡ್‌ನಲ್ಲಿ ಯಾವುದೇ ಧ್ವನಿಮೇಲ್‌ ಸಂಖ್ಯೆಯನ್ನು ಸಂಗ್ರಹಿಸಿಲ್ಲ."</string>
     <string name="add_vm_number_str" msgid="5179510133063168998">"ಸಂಖ್ಯೆಯನ್ನು ಸೇರಿಸಿ"</string>
-    <string name="change_default_dialer_dialog_title" msgid="5861469279421508060">"<xliff:g id="NEW_APP">%s</xliff:g> ಅನ್ನು ನಿಮ್ಮ ಡಿಫಾಲ್ಟ್ ಫೋನ್ ಆ್ಯಪ್ ಆಗಿ ಮಾಡಬೇಕೆ?"</string>
+    <string name="change_default_dialer_dialog_title" msgid="5861469279421508060">"<xliff:g id="NEW_APP">%s</xliff:g> ಅನ್ನು ನಿಮ್ಮ ಡಿಫಾಲ್ಟ್ ಫೋನ್ ಅಪ್ಲಿಕೇಶನ್ ಆಗಿ ಮಾಡುವುದೇ?"</string>
     <string name="change_default_dialer_dialog_affirmative" msgid="8604665314757739550">"ಡಿಫಾಲ್ಟ್ ಹೊಂದಿಸಿ"</string>
     <string name="change_default_dialer_dialog_negative" msgid="8648669840052697821">"ರದ್ದುಮಾಡಿ"</string>
     <string name="change_default_dialer_warning_message" msgid="8461963987376916114">"<xliff:g id="NEW_APP">%s</xliff:g> ಗೆ ನಿಮ್ಮ ಕರೆಗಳ ಎಲ್ಲಾ ಅಂಶಗಳನ್ನು ನಿಯಂತ್ರಿಸಲು ಮತ್ತು ಕರೆಗಳನ್ನು ಮಾಡಲು ಸಾಧ್ಯವಾಗುತ್ತದೆ. ನೀವು ವಿಶ್ವಾಸವಿರಿಸಿರುವಂತಹ ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಮಾತ್ರ ನಿಮ್ಮ ಡಿಫಾಲ್ಟ್ ಅಪ್ಲಿಕೇಶನ್ ಆಗಿ ಹೊಂದಿಸಬೇಕು."</string>
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"ಟೆಲಿಕಾಂ ಡೆವಲಪರ್ ಮೆನು"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"ತುರ್ತು ಕರೆಯಲ್ಲಿರುವಾಗ ಕರೆಗಳನ್ನು ಸ್ವೀಕರಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ."</string>
     <string name="cancel" msgid="6733466216239934756">"ರದ್ದುಮಾಡಿ"</string>
+    <string name="back" msgid="6915955601805550206">"ಹಿಂದಕ್ಕೆ"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"ಇಯರ್‌ಪೀಸ್"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"ಬ್ಲೂಟೂತ್"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"ವೈಯರ್ಡ್ ಹೆಡ್‌ಸೆಟ್"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"ಸ್ಪೀಕರ್"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"ಬಾಹ್ಯ"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"ಅಪರಿಚಿತ"</string>
 </resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index f428191..6b4c2f1 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -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 31ffd48..aa8ce3e 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -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 6d176d2..45c2b70 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -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 b530089..5e8b1f2 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -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 4219487..0433037 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -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 f48e831..4873380 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -109,7 +109,7 @@
     <string name="phone_settings_number_not_in_contact_txt" msgid="2602249106007265757">"Броеви што не се наведени во „Контакти“"</string>
     <string name="phone_settings_number_not_in_contact_summary_txt" msgid="963327038085718969">"Блокирани броеви што не се наведени во вашите „Контакти“"</string>
     <string name="phone_settings_private_num_txt" msgid="6339272760338475619">"Приватно"</string>
-    <string name="phone_settings_private_num_summary_txt" msgid="6755758240544021037">"Блокирај повикувачи со скриен број"</string>
+    <string name="phone_settings_private_num_summary_txt" msgid="6755758240544021037">"Блокирај повикувачи со сокриен број"</string>
     <string name="phone_settings_payphone_txt" msgid="5003987966052543965">"Телефонска говорница"</string>
     <string name="phone_settings_payphone_summary_txt" msgid="3936631076065563665">"Блокирај повици од телефонски говорници"</string>
     <string name="phone_settings_unknown_txt" msgid="3577926178354772728">"Непознато"</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 beb730d..9e6b8ca 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -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 71231dc..2c90998 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -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 dfff80f..263433d 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -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 0c5b1dc..4a8d554 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -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 7a5dbbf..3511bca 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -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 77331ce..fb4dc97 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -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 cdf6c1f..8c02676 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"टेलिकमको विकासकर्ताको मेनु"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"आपत्‌कालीन कल चलिराखेको बेलामा अरु कल स्वीकार गर्न सकिँदैन।"</string>
     <string name="cancel" msgid="6733466216239934756">"रद्द गर्नुहोस्"</string>
+    <string name="back" msgid="6915955601805550206">"पछाडि"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"इयरपस"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"ब्लुटुथ"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"तारसहितको हेडसेट"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"स्पिकर"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"बाह्य"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"अज्ञात"</string>
 </resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 3e7b071..726ab60 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -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 8b52919..c25ec86 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -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 351cf15..65073e2 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -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 caf5fd7..a10d29f 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -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 a1e725e..0b279b4 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -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 10b51b4..a5628c4 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -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 8b4191e..2332d4d 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -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 78f5a6c..139108d 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -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 8ccff4a..e3faf49 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -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 38e7417..f7606ec 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -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 edc41a3..138524b 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -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 80d1d77..0a36a40 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -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 617f8d8..b846841 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -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 41d4d16..acc6dc6 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -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 e6be099..84f7294 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -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 3b812ea..18b5861 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -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 edd0d95..5ed2ebe 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -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 f09de86..678af2d 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -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 a4d5196..495c191 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -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 da9416a..1309682 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -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 551632a..7b81d25 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -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 146d720..afd8d0a 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -27,8 +27,8 @@
     <string name="notification_missedCall_call_back" msgid="7900333283939789732">"واپس کال کریں"</string>
     <string name="notification_missedCall_message" msgid="4054698824390076431">"پیغام"</string>
     <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"کال غیر منسلک کر دیا گیا"</string>
-    <string name="notification_disconnectedCall_body" msgid="600491714584417536">"ایمرجنسی کال کی وجہ سے <xliff:g id="CALLER">%s</xliff:g> کی کال کو غیر منسلک کر دیا گیا ہے۔"</string>
-    <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"ایمرجنسی کال لگائے جانے کی وجہ سے آپ کی کال غیر منسلک ہوگئی ہے۔"</string>
+    <string name="notification_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="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>
@@ -46,7 +46,7 @@
     <string name="respond_via_sms_confirmation_format" msgid="2932395476561267842">"پیغام <xliff:g id="PHONE_NUMBER">%s</xliff:g> کو بھیج دیا گیا۔"</string>
     <string name="respond_via_sms_failure_format" msgid="5198680980054596391">"<xliff:g id="PHONE_NUMBER">%s</xliff:g> پر پیغام نہیں بھیجا جا سکا۔"</string>
     <string name="enable_account_preference_title" msgid="6949224486748457976">"کالنگ اکاؤنٹس"</string>
-    <string name="outgoing_call_not_allowed_user_restriction" msgid="3424338207838851646">"صرف ایمرجنسی کالز کی اجازت ہے۔"</string>
+    <string name="outgoing_call_not_allowed_user_restriction" msgid="3424338207838851646">"صرف ہنگامی کالز کی اجازت ہے۔"</string>
     <string name="outgoing_call_not_allowed_no_permission" msgid="8590468836581488679">"یہ ایپلی کیشن فون کی اجازت کے بغیر باہر جانے والی کالیں نہیں کر سکتی۔"</string>
     <string name="outgoing_call_error_no_phone_number_supplied" msgid="7665135102566099778">"کال کرنے کیلئے، ایک درست نمبر درج کریں۔"</string>
     <string name="duplicate_video_call_not_allowed" msgid="5754746140185781159">"اس وقت کال شامل نہیں کی جا سکتی ہے۔"</string>
@@ -73,11 +73,11 @@
     <string name="non_primary_user" msgid="315564589279622098">"صرف آلہ کا مالک مسدود کردہ نمبرز کو دیکھ سکتا ہے اور ان کا نظم کر سکتا ہے۔"</string>
     <string name="delete_icon_description" msgid="5335959254954774373">"غیر مسدود کریں"</string>
     <string name="blocked_numbers_butter_bar_title" msgid="582982373755950791">"مسدود کرنا عارضی طور پر آف ہے"</string>
-    <string name="blocked_numbers_butter_bar_body" msgid="1261213114919301485">"جب آپ کوئی ایمرجنسی نمبر ڈائل کرتے یا اسے متن بھیجتے ہیں تو انسداد کو آ‌ف کر دیا جاتا ہے تاکہ ایمرجنسی سروسز آپ سے رابطہ کر سکیں۔"</string>
+    <string name="blocked_numbers_butter_bar_body" msgid="1261213114919301485">"جب آپ کوئی ایمرجنسی نمبر ڈائل کرتے یا اسے متن بھیجتے ہیں تو انسداد کو آ‌ف کر دیا جاتا ہے تاکہ ہنگامی سروسز آپ سے رابطہ کر سکیں۔"</string>
     <string name="blocked_numbers_butter_bar_button" msgid="2704456308072489793">"اب دوبارہ فعال کریں"</string>
     <string name="blocked_numbers_number_blocked_message" msgid="4314736791180919167">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> مسدود کر دیا گیا"</string>
     <string name="blocked_numbers_number_unblocked_message" msgid="2933071624674945601">"<xliff:g id="UNBLOCKED_NUMBER">%1$s</xliff:g> غیر مسدود کر دیا گیا"</string>
-    <string name="blocked_numbers_block_emergency_number_message" msgid="4198550501500893890">"ایمرجنسی نمبر مسدود کرنے سے قاصر۔"</string>
+    <string name="blocked_numbers_block_emergency_number_message" msgid="4198550501500893890">"ہنگامی نمبر مسدود کرنے سے قاصر۔"</string>
     <string name="blocked_numbers_number_already_blocked_message" msgid="2301270825735665458">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> پہلے ہی مسدود ہے۔"</string>
     <string name="toast_personal_call_msg" msgid="5817631570381795610">"کال کرنے کیلئے ذاتی ڈائلر استعمال ہو رہا ہے"</string>
     <string name="notification_incoming_call" msgid="1233481138362230894">"<xliff:g id="CALL_FROM">%2$s</xliff:g> کی جانب سے <xliff:g id="CALL_VIA">%1$s</xliff:g> کال"</string>
@@ -118,9 +118,16 @@
     <string name="phone_settings_unavailable_summary_txt" msgid="8221686031038282633">"کالز کو مسدود کریں جہاں یہ نمبر دستیاب نہ ہو"</string>
     <string name="phone_strings_call_blocking_turned_off_notification_title_txt" msgid="2895809176537908791">"کال مسدود کرنا"</string>
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="1713632946174016619">"کال مسدود کرنا غیر فعال ہو گیا ہے"</string>
-    <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"ایمرجنسی کال کی گئی"</string>
-    <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"ایمرجنسی حالت میں جواب دہندگان کو آپ سے رابطہ کرنے کی اجازت دینے کیلئے کال مسدود کرنا غیر فعال ہو گیا ہے۔"</string>
+    <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"ہنگامی کال کی گئی"</string>
+    <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"ہنگامی حالت میں جواب دہندگان کو آپ سے رابطہ کرنے کی اجازت دینے کیلئے کال مسدود کرنا غیر فعال ہو گیا ہے۔"</string>
     <string name="developer_title" msgid="9146088855661672353">"ٹیلی کام ڈویلپر مینیو"</string>
-    <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"ایمرجنسی کال کے دوران کالز نہیں لی جائیں گی۔"</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 0672e36..688b6a7 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -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 7481c22..0920d3b 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -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 2cbdd75..b926e55 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -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 e769d51..e7b1a07 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -28,11 +28,11 @@
     <string name="notification_missedCall_message" msgid="4054698824390076431">"短訊"</string>
     <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"已中斷的通話"</string>
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"因撥打緊急電話緣故,與<xliff:g id="CALLER">%s</xliff:g>的通話已中斷。"</string>
-    <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"因撥打緊急電話緣故,你的通話已中斷。"</string>
+    <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"因撥打緊急電話緣故,您的通話已中斷。"</string>
     <string name="notification_audioProcessing_title" msgid="1619035039880584575">"背景通話"</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="notification_incallservice_not_responding_body" msgid="9209308270131968623">"您使用了裝置隨付的手機應用程式來通話"</string>
     <string name="accessibility_call_muted" msgid="2968461092554300779">"通話已靜音。"</string>
     <string name="accessibility_speakerphone_enabled" msgid="555386652061614267">"擴音器已啟用"</string>
     <string name="respond_via_sms_canned_response_1" msgid="6332561460870382561">"我現在不方便通話,有什麼事呢?"</string>
@@ -56,14 +56,14 @@
     <string name="change_default_dialer_dialog_title" msgid="5861469279421508060">"要將<xliff:g id="NEW_APP">%s</xliff:g>設為預設電話應用程式嗎?"</string>
     <string name="change_default_dialer_dialog_affirmative" msgid="8604665314757739550">"設為預設"</string>
     <string name="change_default_dialer_dialog_negative" msgid="8648669840052697821">"取消"</string>
-    <string name="change_default_dialer_warning_message" msgid="8461963987376916114">"「<xliff:g id="NEW_APP">%s</xliff:g>」將可撥打電話並控制所有相關功能。只有你信任的應用程式,才應設為預設手機應用程式。"</string>
+    <string name="change_default_dialer_warning_message" msgid="8461963987376916114">"「<xliff:g id="NEW_APP">%s</xliff:g>」將可撥打電話並控制所有相關功能。只有您信任的應用程式,才應設為預設手機應用程式。"</string>
     <string name="change_default_call_screening_dialog_title" msgid="5365787219927262408">"要將「<xliff:g id="NEW_APP">%s</xliff:g>」設為預設來電過濾應用程式嗎?"</string>
     <string name="change_default_call_screening_warning_message_for_disable_old_app" msgid="2039830033533243164">"「<xliff:g id="OLD_APP">%s</xliff:g>」無法再篩選來電。"</string>
-    <string name="change_default_call_screening_warning_message" msgid="9020537562292754269">"「<xliff:g id="NEW_APP">%s</xliff:g>」將可查看通訊錄以外來電者的相關資訊,並封鎖這些來電。只有你信任的應用程式才適合設為預設來電過濾應用程式。"</string>
+    <string name="change_default_call_screening_warning_message" msgid="9020537562292754269">"「<xliff:g id="NEW_APP">%s</xliff:g>」將可查看通訊錄以外來電者的相關資訊,並封鎖這些來電。只有您信任的應用程式才適合設為預設來電過濾應用程式。"</string>
     <string name="change_default_call_screening_dialog_affirmative" msgid="7162433828280058647">"設為預設"</string>
     <string name="change_default_call_screening_dialog_negative" msgid="1839266125623106342">"取消"</string>
     <string name="blocked_numbers" msgid="8322134197039865180">"已封鎖的號碼"</string>
-    <string name="blocked_numbers_msg" msgid="2797422132329662697">"你不會收到已封鎖號碼的來電或短訊。"</string>
+    <string name="blocked_numbers_msg" msgid="2797422132329662697">"您不會收到已封鎖號碼的來電或短訊。"</string>
     <string name="block_number" msgid="3784343046852802722">"新增號碼"</string>
     <string name="unblock_dialog_body" msgid="2723393535797217261">"要解除封鎖 <xliff:g id="NUMBER_TO_BLOCK">%1$s</xliff:g> 嗎?"</string>
     <string name="unblock_button" msgid="8732021675729981781">"解除封鎖"</string>
@@ -73,7 +73,7 @@
     <string name="non_primary_user" msgid="315564589279622098">"只有裝置擁有者可查看和管理已封鎖的號碼。"</string>
     <string name="delete_icon_description" msgid="5335959254954774373">"解除封鎖"</string>
     <string name="blocked_numbers_butter_bar_title" msgid="582982373755950791">"暫時關閉封鎖功能"</string>
-    <string name="blocked_numbers_butter_bar_body" msgid="1261213114919301485">"在你撥打或發短訊至緊急號碼後,封鎖功能會停用,以確保緊急服務可與你聯絡。"</string>
+    <string name="blocked_numbers_butter_bar_body" msgid="1261213114919301485">"在您撥打或發短訊至緊急號碼後,封鎖功能會停用,以確保緊急服務可與您聯絡。"</string>
     <string name="blocked_numbers_butter_bar_button" msgid="2704456308072489793">"立即重新啟用"</string>
     <string name="blocked_numbers_number_blocked_message" msgid="4314736791180919167">"已封鎖 <xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g>"</string>
     <string name="blocked_numbers_number_unblocked_message" msgid="2933071624674945601">"已解除對 <xliff:g id="UNBLOCKED_NUMBER">%1$s</xliff:g> 的封鎖"</string>
@@ -82,17 +82,17 @@
     <string name="toast_personal_call_msg" msgid="5817631570381795610">"使用個人撥號器撥打電話"</string>
     <string name="notification_incoming_call" msgid="1233481138362230894">"來自<xliff:g id="CALL_FROM">%2$s</xliff:g>的 <xliff:g id="CALL_VIA">%1$s</xliff:g> 通話"</string>
     <string name="notification_incoming_video_call" msgid="5795968314037063900">"來自<xliff:g id="CALL_FROM">%2$s</xliff:g>的 <xliff:g id="CALL_VIA">%1$s</xliff:g> 視像通話"</string>
-    <string name="answering_ends_other_call" msgid="8653544281903986641">"如果接聽,你的 <xliff:g id="CALL_VIA">%1$s</xliff:g> 通話將會結束"</string>
-    <string name="answering_ends_other_calls" msgid="3702302838456922535">"如果接聽,你的 <xliff:g id="CALL_VIA">%1$s</xliff:g> 通話將會結束"</string>
-    <string name="answering_ends_other_video_call" msgid="8572022039304239958">"如果接聽,你的 <xliff:g id="CALL_VIA">%1$s</xliff:g> 視像通話將會結束"</string>
-    <string name="answering_ends_other_managed_call" msgid="4031778317409881805">"如果接聽,你進行中的通話將會結束"</string>
-    <string name="answering_ends_other_managed_calls" msgid="3974069768615307659">"如果接聽,你進行中的通話將會結束"</string>
-    <string name="answering_ends_other_managed_video_call" msgid="1988508241432031327">"如果接聽,你進行中的視像通話將會結束"</string>
+    <string name="answering_ends_other_call" msgid="8653544281903986641">"如果接聽,您的 <xliff:g id="CALL_VIA">%1$s</xliff:g> 通話將會結束"</string>
+    <string name="answering_ends_other_calls" msgid="3702302838456922535">"如果接聽,您的 <xliff:g id="CALL_VIA">%1$s</xliff:g> 通話將會結束"</string>
+    <string name="answering_ends_other_video_call" msgid="8572022039304239958">"如果接聽,您的 <xliff:g id="CALL_VIA">%1$s</xliff:g> 視像通話將會結束"</string>
+    <string name="answering_ends_other_managed_call" msgid="4031778317409881805">"如果接聽,您進行中的通話將會結束"</string>
+    <string name="answering_ends_other_managed_calls" msgid="3974069768615307659">"如果接聽,您進行中的通話將會結束"</string>
+    <string name="answering_ends_other_managed_video_call" msgid="1988508241432031327">"如果接聽,您進行中的視像通話將會結束"</string>
     <string name="answer_incoming_call" msgid="2045888814782215326">"接聽"</string>
     <string name="decline_incoming_call" msgid="922147089348451310">"拒絕"</string>
     <string name="cant_call_due_to_no_supported_service" msgid="1635626384149947077">"沒有通話帳戶支援這類通話,因此無法撥打電話。"</string>
-    <string name="cant_call_due_to_ongoing_call" msgid="8004235328451385493">"由於你已在進行 <xliff:g id="OTHER_CALL">%1$s</xliff:g> 通話,因此無法撥打電話。"</string>
-    <string name="cant_call_due_to_ongoing_calls" msgid="6379163795277824868">"由於你已在進行 <xliff:g id="OTHER_CALL">%1$s</xliff:g> 通話,因此無法撥打電話。"</string>
+    <string name="cant_call_due_to_ongoing_call" msgid="8004235328451385493">"由於您已在進行 <xliff:g id="OTHER_CALL">%1$s</xliff:g> 通話,因此無法撥打電話。"</string>
+    <string name="cant_call_due_to_ongoing_calls" msgid="6379163795277824868">"由於您已在進行 <xliff:g id="OTHER_CALL">%1$s</xliff:g> 通話,因此無法撥打電話。"</string>
     <string name="cant_call_due_to_ongoing_unknown_call" msgid="8243532328969433172">"由於已在另一個應用程式中進行通話,因此無法撥打電話。"</string>
     <string name="notification_channel_incoming_call" msgid="5245550964701715662">"來電"</string>
     <string name="notification_channel_missed_call" msgid="7168893015283909012">"未接來電"</string>
@@ -100,11 +100,11 @@
     <string name="notification_channel_background_calls" msgid="7785659903711350506">"背景通話"</string>
     <string name="notification_channel_disconnected_calls" msgid="8228636543997645757">"已中斷的通話"</string>
     <string name="notification_channel_in_call_service_crash" msgid="7313237519166984267">"當機的手機應用程式"</string>
-    <string name="alert_outgoing_call" msgid="5319895109298927431">"如果撥打此電話,你的 <xliff:g id="OTHER_APP">%1$s</xliff:g> 通話將會結束。"</string>
+    <string name="alert_outgoing_call" msgid="5319895109298927431">"如果撥打此電話,您的 <xliff:g id="OTHER_APP">%1$s</xliff:g> 通話將會結束。"</string>
     <string name="alert_redirect_outgoing_call_or_not" msgid="665409645789521636">"選擇如何撥打此電話"</string>
     <string name="alert_place_outgoing_call_with_redirection" msgid="5221065030959024121">"使用「<xliff:g id="OTHER_APP">%1$s</xliff:g>」將通話重新導向"</string>
     <string name="alert_place_unredirect_outgoing_call" msgid="2467608535225764006">"使用我的電話號碼撥打"</string>
-    <string name="alert_redirect_outgoing_call_timeout" msgid="5568101425637373060">"<xliff:g id="OTHER_APP">%1$s</xliff:g>無法撥打電話。建議你使用其他通話重新導向應用程式,或向開發人員求助。"</string>
+    <string name="alert_redirect_outgoing_call_timeout" msgid="5568101425637373060">"<xliff:g id="OTHER_APP">%1$s</xliff:g>無法撥打電話。建議您使用其他通話重新導向應用程式,或向開發人員求助。"</string>
     <string name="phone_settings_call_blocking_txt" msgid="7311523114822507178">"來電封鎖"</string>
     <string name="phone_settings_number_not_in_contact_txt" msgid="2602249106007265757">"不在通訊錄中的號碼"</string>
     <string name="phone_settings_number_not_in_contact_summary_txt" msgid="963327038085718969">"封鎖不在通訊錄中的號碼"</string>
@@ -119,8 +119,15 @@
     <string name="phone_strings_call_blocking_turned_off_notification_title_txt" msgid="2895809176537908791">"來電封鎖"</string>
     <string name="phone_strings_call_blocking_turned_off_notification_text_txt" msgid="1713632946174016619">"已停用來電封鎖功能"</string>
     <string name="phone_strings_emergency_call_made_dialog_title_txt" msgid="6629412508584507377">"已撥緊急電話"</string>
-    <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"已停用來電封鎖功能,以便救援人員與你聯絡。"</string>
+    <string name="phone_strings_emergency_call_made_dialog_call_blocking_text_txt" msgid="3140411733995271126">"已停用來電封鎖功能,以便救援人員與您聯絡。"</string>
     <string name="developer_title" msgid="9146088855661672353">"電信開發商選單"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"使用緊急電話期間無法接聽電話。"</string>
     <string name="cancel" msgid="6733466216239934756">"取消"</string>
+    <string name="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 26a8db9..21b8ae9 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -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 099173f..fbde58b 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -123,4 +123,11 @@
     <string name="developer_title" msgid="9146088855661672353">"Imenyu yonjiniyela we-Telecom"</string>
     <string name="toast_emergency_can_not_pull_call" msgid="9074229465338410869">"Amakholi awakwazi ukuthathwa ngesikhathi ukukholi yesimo esiphuthumayo."</string>
     <string name="cancel" msgid="6733466216239934756">"Khansela"</string>
+    <string name="back" msgid="6915955601805550206">"Emuva"</string>
+    <string name="callendpoint_name_earpiece" msgid="7047285080319678594">"Isipikha sendlebe"</string>
+    <string name="callendpoint_name_bluetooth" msgid="210210953208913172">"I-Bluetooth"</string>
+    <string name="callendpoint_name_wiredheadset" msgid="6860787176412079742">"I-headset enentambo"</string>
+    <string name="callendpoint_name_speaker" msgid="1971760468695323189">"Isipikha"</string>
+    <string name="callendpoint_name_streaming" msgid="2337595450408275576">"Okungaphandle"</string>
+    <string name="callendpoint_name_unknown" msgid="2199074708477193852">"Akwaziwa"</string>
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index bf5abca..ec278f0 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -321,6 +321,10 @@
     <string name="notification_channel_disconnected_calls">Disconnected calls</string>
     <!-- Notification channel name for a channel containing crashed phone apps service notifications. -->
     <string name="notification_channel_in_call_service_crash">Crashed phone apps</string>
+    <!-- Notification channel name for a channel containing notifications related to call streaming.
+         Call streaming is a feature where an app can use another device like a tablet to see and
+         control a call taking place on their phone. -->
+    <string name="notification_channel_call_streaming">Call streaming</string>
 
     <!-- Alert dialog content used to inform the user that placing a new outgoing call will end the
          ongoing call in the app "other_app". -->
@@ -381,4 +385,34 @@
     <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>
+
+    <!-- The content of a notification shown when a call is being streamed to another device.
+         Call streaming is a feature where a user can see and interact with a call from another
+         device like a tablet while the call takes place on their phone. -->
+    <string name="call_streaming_notification_body">Streaming audio to other device</string>
+    <!-- A notification action which is shown when a call is being streamed to another device.
+         Tapping the action will hang up the call.
+         Call streaming is a feature where a user can see and interact with a call from another
+         device like a tablet while the call takes place on their phone. -->
+    <string name="call_streaming_notification_action_hang_up">Hang up</string>
+    <!-- A notification action which is shown when a call is being streamed to another device.
+         Tapping the action will move the call back to the phone from the device it is being
+         streamed to.
+         Call streaming is a feature where a user can see and interact with a call from another
+         device like a tablet while the call takes place on their phone. -->
+    <string name="call_streaming_notification_action_switch_here">Switch here</string>
 </resources>
diff --git a/src/com/android/server/telecom/Analytics.java b/src/com/android/server/telecom/Analytics.java
index 145e9b4..bbcf858 100644
--- a/src/com/android/server/telecom/Analytics.java
+++ b/src/com/android/server/telecom/Analytics.java
@@ -72,96 +72,101 @@
     private static final String CLEAR_ANALYTICS_ARG = "clear";
 
     public static final Map<String, Integer> sLogEventToAnalyticsEvent = Map.ofEntries(
-                entry(LogUtils.Events.SET_SELECT_PHONE_ACCOUNT,
-                        AnalyticsEvent.SET_SELECT_PHONE_ACCOUNT),
-                entry(LogUtils.Events.REQUEST_HOLD, AnalyticsEvent.REQUEST_HOLD),
-                entry(LogUtils.Events.REQUEST_UNHOLD, AnalyticsEvent.REQUEST_UNHOLD),
-                entry(LogUtils.Events.SWAP, AnalyticsEvent.SWAP),
-                entry(LogUtils.Events.SKIP_RINGING, AnalyticsEvent.SKIP_RINGING),
-                entry(LogUtils.Events.CONFERENCE_WITH, AnalyticsEvent.CONFERENCE_WITH),
-                entry(LogUtils.Events.SPLIT_FROM_CONFERENCE, AnalyticsEvent.SPLIT_CONFERENCE),
-                entry(LogUtils.Events.SET_PARENT, AnalyticsEvent.SET_PARENT),
-                entry(LogUtils.Events.MUTE, AnalyticsEvent.MUTE),
-                entry(LogUtils.Events.UNMUTE, AnalyticsEvent.UNMUTE),
-                entry(LogUtils.Events.AUDIO_ROUTE_BT, AnalyticsEvent.AUDIO_ROUTE_BT),
-                entry(LogUtils.Events.AUDIO_ROUTE_EARPIECE, AnalyticsEvent.AUDIO_ROUTE_EARPIECE),
-                entry(LogUtils.Events.AUDIO_ROUTE_HEADSET, AnalyticsEvent.AUDIO_ROUTE_HEADSET),
-                entry(LogUtils.Events.AUDIO_ROUTE_SPEAKER, AnalyticsEvent.AUDIO_ROUTE_SPEAKER),
-                entry(LogUtils.Events.SILENCE, AnalyticsEvent.SILENCE),
-                entry(LogUtils.Events.SCREENING_COMPLETED, AnalyticsEvent.SCREENING_COMPLETED),
-                entry(LogUtils.Events.BLOCK_CHECK_FINISHED, AnalyticsEvent.BLOCK_CHECK_FINISHED),
-                entry(LogUtils.Events.DIRECT_TO_VM_FINISHED, AnalyticsEvent.DIRECT_TO_VM_FINISHED),
-                entry(LogUtils.Events.REMOTELY_HELD, AnalyticsEvent.REMOTELY_HELD),
-                entry(LogUtils.Events.REMOTELY_UNHELD, AnalyticsEvent.REMOTELY_UNHELD),
-                entry(LogUtils.Events.REQUEST_PULL, AnalyticsEvent.REQUEST_PULL),
-                entry(LogUtils.Events.REQUEST_ACCEPT, AnalyticsEvent.REQUEST_ACCEPT),
-                entry(LogUtils.Events.REQUEST_REJECT, AnalyticsEvent.REQUEST_REJECT),
-                entry(LogUtils.Events.SET_ACTIVE, AnalyticsEvent.SET_ACTIVE),
-                entry(LogUtils.Events.SET_DISCONNECTED, AnalyticsEvent.SET_DISCONNECTED),
-                entry(LogUtils.Events.SET_HOLD, AnalyticsEvent.SET_HOLD),
-                entry(LogUtils.Events.SET_DIALING, AnalyticsEvent.SET_DIALING),
-                entry(LogUtils.Events.START_CONNECTION, AnalyticsEvent.START_CONNECTION),
-                entry(LogUtils.Events.BIND_CS, AnalyticsEvent.BIND_CS),
-                entry(LogUtils.Events.CS_BOUND, AnalyticsEvent.CS_BOUND),
-                entry(LogUtils.Events.SCREENING_SENT, AnalyticsEvent.SCREENING_SENT),
-                entry(LogUtils.Events.DIRECT_TO_VM_INITIATED,
-                        AnalyticsEvent.DIRECT_TO_VM_INITIATED),
-                entry(LogUtils.Events.BLOCK_CHECK_INITIATED, AnalyticsEvent.BLOCK_CHECK_INITIATED),
-                entry(LogUtils.Events.FILTERING_INITIATED, AnalyticsEvent.FILTERING_INITIATED),
-                entry(LogUtils.Events.FILTERING_COMPLETED, AnalyticsEvent.FILTERING_COMPLETED),
-                entry(LogUtils.Events.FILTERING_TIMED_OUT, AnalyticsEvent.FILTERING_TIMED_OUT));
+            entry(LogUtils.Events.SET_SELECT_PHONE_ACCOUNT,
+                    AnalyticsEvent.SET_SELECT_PHONE_ACCOUNT),
+            entry(LogUtils.Events.REQUEST_HOLD, AnalyticsEvent.REQUEST_HOLD),
+            entry(LogUtils.Events.REQUEST_UNHOLD, AnalyticsEvent.REQUEST_UNHOLD),
+            entry(LogUtils.Events.SWAP, AnalyticsEvent.SWAP),
+            entry(LogUtils.Events.SKIP_RINGING, AnalyticsEvent.SKIP_RINGING),
+            entry(LogUtils.Events.CONFERENCE_WITH, AnalyticsEvent.CONFERENCE_WITH),
+            entry(LogUtils.Events.SPLIT_FROM_CONFERENCE, AnalyticsEvent.SPLIT_CONFERENCE),
+            entry(LogUtils.Events.SET_PARENT, AnalyticsEvent.SET_PARENT),
+            entry(LogUtils.Events.MUTE, AnalyticsEvent.MUTE),
+            entry(LogUtils.Events.UNMUTE, AnalyticsEvent.UNMUTE),
+            entry(LogUtils.Events.AUDIO_ROUTE_BT, AnalyticsEvent.AUDIO_ROUTE_BT),
+            entry(LogUtils.Events.AUDIO_ROUTE_EARPIECE, AnalyticsEvent.AUDIO_ROUTE_EARPIECE),
+            entry(LogUtils.Events.AUDIO_ROUTE_HEADSET, AnalyticsEvent.AUDIO_ROUTE_HEADSET),
+            entry(LogUtils.Events.AUDIO_ROUTE_SPEAKER, AnalyticsEvent.AUDIO_ROUTE_SPEAKER),
+            entry(LogUtils.Events.SILENCE, AnalyticsEvent.SILENCE),
+            entry(LogUtils.Events.SCREENING_COMPLETED, AnalyticsEvent.SCREENING_COMPLETED),
+            entry(LogUtils.Events.BLOCK_CHECK_FINISHED, AnalyticsEvent.BLOCK_CHECK_FINISHED),
+            entry(LogUtils.Events.DIRECT_TO_VM_FINISHED, AnalyticsEvent.DIRECT_TO_VM_FINISHED),
+            entry(LogUtils.Events.REMOTELY_HELD, AnalyticsEvent.REMOTELY_HELD),
+            entry(LogUtils.Events.REMOTELY_UNHELD, AnalyticsEvent.REMOTELY_UNHELD),
+            entry(LogUtils.Events.REQUEST_PULL, AnalyticsEvent.REQUEST_PULL),
+            entry(LogUtils.Events.REQUEST_ACCEPT, AnalyticsEvent.REQUEST_ACCEPT),
+            entry(LogUtils.Events.REQUEST_REJECT, AnalyticsEvent.REQUEST_REJECT),
+            entry(LogUtils.Events.SET_ACTIVE, AnalyticsEvent.SET_ACTIVE),
+            entry(LogUtils.Events.SET_DISCONNECTED, AnalyticsEvent.SET_DISCONNECTED),
+            entry(LogUtils.Events.SET_HOLD, AnalyticsEvent.SET_HOLD),
+            entry(LogUtils.Events.SET_DIALING, AnalyticsEvent.SET_DIALING),
+            entry(LogUtils.Events.START_CONNECTION, AnalyticsEvent.START_CONNECTION),
+            entry(LogUtils.Events.BIND_CS, AnalyticsEvent.BIND_CS),
+            entry(LogUtils.Events.CS_BOUND, AnalyticsEvent.CS_BOUND),
+            entry(LogUtils.Events.SCREENING_SENT, AnalyticsEvent.SCREENING_SENT),
+            entry(LogUtils.Events.DIRECT_TO_VM_INITIATED,
+                    AnalyticsEvent.DIRECT_TO_VM_INITIATED),
+            entry(LogUtils.Events.BLOCK_CHECK_INITIATED, AnalyticsEvent.BLOCK_CHECK_INITIATED),
+            entry(LogUtils.Events.FILTERING_INITIATED, AnalyticsEvent.FILTERING_INITIATED),
+            entry(LogUtils.Events.FILTERING_COMPLETED, AnalyticsEvent.FILTERING_COMPLETED),
+            entry(LogUtils.Events.FILTERING_TIMED_OUT, AnalyticsEvent.FILTERING_TIMED_OUT),
+            entry(LogUtils.Events.DND_PRE_CHECK_INITIATED, AnalyticsEvent.DND_CHECK_INITIATED),
+            entry(LogUtils.Events.DND_PRE_CHECK_COMPLETED, AnalyticsEvent.DND_CHECK_COMPLETED));
 
     public static final Map<String, Integer> sLogSessionToSessionId = Map.ofEntries(
-                entry(LogUtils.Sessions.ICA_ANSWER_CALL, SessionTiming.ICA_ANSWER_CALL),
-                entry(LogUtils.Sessions.ICA_REJECT_CALL, SessionTiming.ICA_REJECT_CALL),
-                entry(LogUtils.Sessions.ICA_DISCONNECT_CALL, SessionTiming.ICA_DISCONNECT_CALL),
-                entry(LogUtils.Sessions.ICA_HOLD_CALL, SessionTiming.ICA_HOLD_CALL),
-                entry(LogUtils.Sessions.ICA_UNHOLD_CALL, SessionTiming.ICA_UNHOLD_CALL),
-                entry(LogUtils.Sessions.ICA_MUTE, SessionTiming.ICA_MUTE),
-                entry(LogUtils.Sessions.ICA_SET_AUDIO_ROUTE, SessionTiming.ICA_SET_AUDIO_ROUTE),
-                entry(LogUtils.Sessions.ICA_CONFERENCE, SessionTiming.ICA_CONFERENCE),
-                entry(LogUtils.Sessions.CSW_HANDLE_CREATE_CONNECTION_COMPLETE,
-                        SessionTiming.CSW_HANDLE_CREATE_CONNECTION_COMPLETE),
-                entry(LogUtils.Sessions.CSW_SET_ACTIVE, SessionTiming.CSW_SET_ACTIVE),
-                entry(LogUtils.Sessions.CSW_SET_RINGING, SessionTiming.CSW_SET_RINGING),
-                entry(LogUtils.Sessions.CSW_SET_DIALING, SessionTiming.CSW_SET_DIALING),
-                entry(LogUtils.Sessions.CSW_SET_DISCONNECTED, SessionTiming.CSW_SET_DISCONNECTED),
-                entry(LogUtils.Sessions.CSW_SET_ON_HOLD, SessionTiming.CSW_SET_ON_HOLD),
-                entry(LogUtils.Sessions.CSW_REMOVE_CALL, SessionTiming.CSW_REMOVE_CALL),
-                entry(LogUtils.Sessions.CSW_SET_IS_CONFERENCED, SessionTiming.CSW_SET_IS_CONFERENCED),
-                entry(LogUtils.Sessions.CSW_ADD_CONFERENCE_CALL,
-                        SessionTiming.CSW_ADD_CONFERENCE_CALL));
+            entry(LogUtils.Sessions.ICA_ANSWER_CALL, SessionTiming.ICA_ANSWER_CALL),
+            entry(LogUtils.Sessions.ICA_REJECT_CALL, SessionTiming.ICA_REJECT_CALL),
+            entry(LogUtils.Sessions.ICA_DISCONNECT_CALL, SessionTiming.ICA_DISCONNECT_CALL),
+            entry(LogUtils.Sessions.ICA_HOLD_CALL, SessionTiming.ICA_HOLD_CALL),
+            entry(LogUtils.Sessions.ICA_UNHOLD_CALL, SessionTiming.ICA_UNHOLD_CALL),
+            entry(LogUtils.Sessions.ICA_MUTE, SessionTiming.ICA_MUTE),
+            entry(LogUtils.Sessions.ICA_SET_AUDIO_ROUTE, SessionTiming.ICA_SET_AUDIO_ROUTE),
+            entry(LogUtils.Sessions.ICA_CONFERENCE, SessionTiming.ICA_CONFERENCE),
+            entry(LogUtils.Sessions.CSW_HANDLE_CREATE_CONNECTION_COMPLETE,
+                    SessionTiming.CSW_HANDLE_CREATE_CONNECTION_COMPLETE),
+            entry(LogUtils.Sessions.CSW_SET_ACTIVE, SessionTiming.CSW_SET_ACTIVE),
+            entry(LogUtils.Sessions.CSW_SET_RINGING, SessionTiming.CSW_SET_RINGING),
+            entry(LogUtils.Sessions.CSW_SET_DIALING, SessionTiming.CSW_SET_DIALING),
+            entry(LogUtils.Sessions.CSW_SET_DISCONNECTED, SessionTiming.CSW_SET_DISCONNECTED),
+            entry(LogUtils.Sessions.CSW_SET_ON_HOLD, SessionTiming.CSW_SET_ON_HOLD),
+            entry(LogUtils.Sessions.CSW_REMOVE_CALL, SessionTiming.CSW_REMOVE_CALL),
+            entry(LogUtils.Sessions.CSW_SET_IS_CONFERENCED, SessionTiming.CSW_SET_IS_CONFERENCED),
+            entry(LogUtils.Sessions.CSW_ADD_CONFERENCE_CALL,
+                    SessionTiming.CSW_ADD_CONFERENCE_CALL));
 
     public static final Map<String, Integer> sLogEventTimingToAnalyticsEventTiming = Map.ofEntries(
-                entry(LogUtils.Events.Timings.ACCEPT_TIMING,
-                        ParcelableCallAnalytics.EventTiming.ACCEPT_TIMING),
-                entry(LogUtils.Events.Timings.REJECT_TIMING,
-                        ParcelableCallAnalytics.EventTiming.REJECT_TIMING),
-                entry(LogUtils.Events.Timings.DISCONNECT_TIMING,
-                        ParcelableCallAnalytics.EventTiming.DISCONNECT_TIMING),
-                entry(LogUtils.Events.Timings.HOLD_TIMING,
-                        ParcelableCallAnalytics.EventTiming.HOLD_TIMING),
-                entry(LogUtils.Events.Timings.UNHOLD_TIMING,
-                        ParcelableCallAnalytics.EventTiming.UNHOLD_TIMING),
-                entry(LogUtils.Events.Timings.OUTGOING_TIME_TO_DIALING_TIMING,
-                        ParcelableCallAnalytics.EventTiming.OUTGOING_TIME_TO_DIALING_TIMING),
-                entry(LogUtils.Events.Timings.BIND_CS_TIMING,
-                        ParcelableCallAnalytics.EventTiming.BIND_CS_TIMING),
-                entry(LogUtils.Events.Timings.SCREENING_COMPLETED_TIMING,
-                        ParcelableCallAnalytics.EventTiming.SCREENING_COMPLETED_TIMING),
-                entry(LogUtils.Events.Timings.DIRECT_TO_VM_FINISHED_TIMING,
-                        ParcelableCallAnalytics.EventTiming.DIRECT_TO_VM_FINISHED_TIMING),
-                entry(LogUtils.Events.Timings.BLOCK_CHECK_FINISHED_TIMING,
-                        ParcelableCallAnalytics.EventTiming.BLOCK_CHECK_FINISHED_TIMING),
-                entry(LogUtils.Events.Timings.FILTERING_COMPLETED_TIMING,
-                        ParcelableCallAnalytics.EventTiming.FILTERING_COMPLETED_TIMING),
-                entry(LogUtils.Events.Timings.FILTERING_TIMED_OUT_TIMING,
-                        ParcelableCallAnalytics.EventTiming.FILTERING_TIMED_OUT_TIMING),
-                entry(LogUtils.Events.Timings.START_CONNECTION_TO_REQUEST_DISCONNECT_TIMING,
-                        ParcelableCallAnalytics.EventTiming.
-                                START_CONNECTION_TO_REQUEST_DISCONNECT_TIMING));
+            entry(LogUtils.Events.Timings.ACCEPT_TIMING,
+                    ParcelableCallAnalytics.EventTiming.ACCEPT_TIMING),
+            entry(LogUtils.Events.Timings.REJECT_TIMING,
+                    ParcelableCallAnalytics.EventTiming.REJECT_TIMING),
+            entry(LogUtils.Events.Timings.DISCONNECT_TIMING,
+                    ParcelableCallAnalytics.EventTiming.DISCONNECT_TIMING),
+            entry(LogUtils.Events.Timings.HOLD_TIMING,
+                    ParcelableCallAnalytics.EventTiming.HOLD_TIMING),
+            entry(LogUtils.Events.Timings.UNHOLD_TIMING,
+                    ParcelableCallAnalytics.EventTiming.UNHOLD_TIMING),
+            entry(LogUtils.Events.Timings.OUTGOING_TIME_TO_DIALING_TIMING,
+                    ParcelableCallAnalytics.EventTiming.OUTGOING_TIME_TO_DIALING_TIMING),
+            entry(LogUtils.Events.Timings.BIND_CS_TIMING,
+                    ParcelableCallAnalytics.EventTiming.BIND_CS_TIMING),
+            entry(LogUtils.Events.Timings.SCREENING_COMPLETED_TIMING,
+                    ParcelableCallAnalytics.EventTiming.SCREENING_COMPLETED_TIMING),
+            entry(LogUtils.Events.Timings.DIRECT_TO_VM_FINISHED_TIMING,
+                    ParcelableCallAnalytics.EventTiming.DIRECT_TO_VM_FINISHED_TIMING),
+            entry(LogUtils.Events.Timings.BLOCK_CHECK_FINISHED_TIMING,
+                    ParcelableCallAnalytics.EventTiming.BLOCK_CHECK_FINISHED_TIMING),
+            entry(LogUtils.Events.Timings.FILTERING_COMPLETED_TIMING,
+                    ParcelableCallAnalytics.EventTiming.FILTERING_COMPLETED_TIMING),
+            entry(LogUtils.Events.Timings.FILTERING_TIMED_OUT_TIMING,
+                    ParcelableCallAnalytics.EventTiming.FILTERING_TIMED_OUT_TIMING),
+            entry(LogUtils.Events.Timings.START_CONNECTION_TO_REQUEST_DISCONNECT_TIMING,
+                    ParcelableCallAnalytics.EventTiming.
+                            START_CONNECTION_TO_REQUEST_DISCONNECT_TIMING),
+            entry(LogUtils.Events.Timings.DND_PRE_CHECK_COMPLETED_TIMING,
+                    ParcelableCallAnalytics.EventTiming.DND_PRE_CALL_PRE_CHECK_TIMING));
 
     public static final Map<Integer, String> sSessionIdToLogSession = new HashMap<>();
+
     static {
         for (Map.Entry<String, Integer> e : sLogSessionToSessionId.entrySet()) {
             sSessionIdToLogSession.put(e.getValue(), e.getKey());
@@ -228,12 +233,12 @@
         public long startTime;  // start time in milliseconds since the epoch. 0 if not yet set.
         public long endTime;  // end time in milliseconds since the epoch. 0 if not yet set.
         public int callDirection;  // one of UNKNOWN_DIRECTION, INCOMING_DIRECTION,
-                                   // or OUTGOING_DIRECTION.
+        // or OUTGOING_DIRECTION.
         public boolean isAdditionalCall = false;  // true if the call came in while another call was
-                                                  // in progress or if the user dialed this call
-                                                  // while in the middle of another call.
+        // in progress or if the user dialed this call
+        // while in the middle of another call.
         public boolean isInterrupted = false;  // true if the call was interrupted by an incoming
-                                               // or outgoing call.
+        // or outgoing call.
         public int callTechnologies;  // bitmask denoting which technologies a call used.
 
         // true if the Telecom Call object was created from an existing connection via
@@ -432,17 +437,17 @@
             TelecomLogClass.CallLog analyticsProto = toProto();
             List<ParcelableCallAnalytics.AnalyticsEvent> events =
                     Arrays.stream(analyticsProto.callEvents)
-                    .map(callEventProto -> new ParcelableCallAnalytics.AnalyticsEvent(
-                                callEventProto.getEventName(),
-                                callEventProto.getTimeSinceLastEventMillis())
-                    ).collect(Collectors.toList());
+                            .map(callEventProto -> new ParcelableCallAnalytics.AnalyticsEvent(
+                                    callEventProto.getEventName(),
+                                    callEventProto.getTimeSinceLastEventMillis())
+                            ).collect(Collectors.toList());
 
             List<ParcelableCallAnalytics.EventTiming> timings =
                     Arrays.stream(analyticsProto.callTimings)
-                    .map(callTimingProto -> new ParcelableCallAnalytics.EventTiming(
-                            callTimingProto.getTimingName(),
-                            callTimingProto.getTimeMillis())
-                    ).collect(Collectors.toList());
+                            .map(callTimingProto -> new ParcelableCallAnalytics.EventTiming(
+                                    callTimingProto.getTimingName(),
+                                    callTimingProto.getTimeMillis())
+                            ).collect(Collectors.toList());
 
             ParcelableCallAnalytics result = new ParcelableCallAnalytics(
                     // rounds down to nearest 5 minute mark
@@ -498,7 +503,7 @@
                     .setConnectionProperties(callProperties)
                     .setCallSource(callSource);
 
-            result.connectionService = new String[] {connectionService};
+            result.connectionService = new String[]{connectionService};
             if (callEvents != null) {
                 result.callEvents = convertLogEventsToProtoEvents(callEvents.getEvents());
                 result.callTimings = callEvents.extractEventTimings().stream()
@@ -547,7 +552,6 @@
         }
 
         private String getMissedReasonString() {
-            //TODO: Implement this
             StringBuilder s =  new StringBuilder();
             s.append('[');
             if ((missedReason & AUTO_MISSED_EMERGENCY_CALL) != 0) {
@@ -608,6 +612,7 @@
             }
         }
     }
+
     public static final String TAG = "TelecomAnalytics";
 
     // Constants for call direction
@@ -842,13 +847,13 @@
     }
 
     @VisibleForTesting
-    public static long roundToOneSigFig(long val)  {
+    public static long roundToOneSigFig(long val) {
         if (val == 0) {
             return val;
         }
         int logVal = (int) Math.floor(Math.log10(val < 0 ? -val : val));
         double s = Math.pow(10, logVal);
-        double dec =  val / s;
+        double dec = val / s;
         return (long) (Math.round(dec) * s);
     }
 }
diff --git a/src/com/android/server/telecom/AnomalyReporterAdapter.java b/src/com/android/server/telecom/AnomalyReporterAdapter.java
new file mode 100644
index 0000000..7c21419
--- /dev/null
+++ b/src/com/android/server/telecom/AnomalyReporterAdapter.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom;
+
+import java.util.UUID;
+
+/**
+ * Interface to avoid static calls to AnomalyReporter. Add methods to this interface as needed for
+ * refactoring.
+ */
+public interface AnomalyReporterAdapter {
+    void reportAnomaly(UUID eventId, String description);
+}
diff --git a/src/com/android/server/telecom/AnomalyReporterAdapterImpl.java b/src/com/android/server/telecom/AnomalyReporterAdapterImpl.java
new file mode 100644
index 0000000..c34d211
--- /dev/null
+++ b/src/com/android/server/telecom/AnomalyReporterAdapterImpl.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom;
+
+import android.telephony.AnomalyReporter;
+import java.util.UUID;
+
+public class AnomalyReporterAdapterImpl implements AnomalyReporterAdapter {
+    @Override
+    public void reportAnomaly(UUID eventId, String description) {
+        AnomalyReporter.reportAnomaly(eventId, description);
+    }
+}
diff --git a/src/com/android/server/telecom/AsyncRingtonePlayer.java b/src/com/android/server/telecom/AsyncRingtonePlayer.java
index 7f51f1b..3fbac1f 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,50 @@
      * 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;
             }
-
-            // 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 */);
+            Ringtone ringtone = null;
+            boolean hasStopped = false;
+            try {
+                ringtone = ringtoneSupplier.get();
+                // Ringtone supply can be slow. Re-check for stop event.
+                if (mHandler.hasMessages(EVENT_STOP)) {
+                    hasStopped = true;
+                    ringtone.stop();  // proactively release the ringtone.
                     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, hasStopped);
             }
-
-            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 +210,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..dd8e7e8 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) {}
@@ -306,6 +318,12 @@
      */
     private long mCreationTimeMillis;
 
+    /**
+     * The elapsed realtime millis when this call was created; this can be used to determine how
+     * long has elapsed since the call was first created.
+     */
+    private long mCreationElapsedRealtimeMillis;
+
     /** The time this call was made active. */
     private long mConnectTimeMillis = 0;
 
@@ -344,7 +362,7 @@
 
     private PhoneAccountHandle mRemotePhoneAccountHandle;
 
-    private UserHandle mInitiatingUser;
+    private UserHandle mAssociatedUser;
 
     private final Handler mHandler = new Handler(Looper.getMainLooper());
 
@@ -353,6 +371,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,8 +410,20 @@
      */
     private ConnectionServiceWrapper mConnectionService;
 
+    private TransactionalServiceWrapper mTransactionalService;
+
     private boolean mIsEmergencyCall;
 
+    /**
+     * Flag indicating if ECBM is active for the target phone account. This only applies to MT calls
+     * in the scenario of work profiles (when the profile is paused and the user has only registered
+     * a work sim). Normally, MT calls made to the work sim should be rejected when the work apps
+     * are paused. However, when the admin makes a MO ecall, ECBM should be enabled for that sim to
+     * allow non-emergency MT calls. MO calls don't apply because the phone account would be
+     * rejected from selection if the owner is not placing the call.
+     */
+    private boolean mIsInECBM;
+
     // The Call is considered an emergency call for testing, but will not actually connect to
     // emergency services.
     private boolean mIsTestEmergencyCall;
@@ -485,12 +526,15 @@
     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;
     private boolean mWasHighDefAudio = false;
     private boolean mWasWifi = false;
     private boolean mWasVolte = false;
+    private boolean mDestroyed = false;
 
     // For conferences which support merge/swap at their level, we retain a notion of an active
     // call. This is used for BluetoothPhoneService.  In order to support hold/merge, it must have
@@ -529,6 +573,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
@@ -760,8 +830,8 @@
                 ? PhoneNumberUtils.extractPostDialPortion(handle.getSchemeSpecificPart()) : "";
         mGatewayInfo = gatewayInfo;
         setConnectionManagerPhoneAccount(connectionManagerPhoneAccountHandle);
-        setTargetPhoneAccount(targetPhoneAccountHandle);
         mCallDirection = callDirection;
+        setTargetPhoneAccount(targetPhoneAccountHandle);
         mIsConference = isConference;
         mShouldAttachToExistingConnection = shouldAttachToExistingConnection
                 || callDirection == CALL_DIRECTION_INCOMING;
@@ -769,8 +839,11 @@
         mClockProxy = clockProxy;
         mToastFactory = toastFactory;
         mCreationTimeMillis = mClockProxy.currentTimeMillis();
+        mCreationElapsedRealtimeMillis = mClockProxy.elapsedRealtime();
         mMissedReason = MISSED_REASON_NOT_MISSED;
         mStartRingTime = 0;
+
+        mCallStateChangedAtomWriter.setExistingCallCount(callsManager.getCalls().size());
     }
 
     /**
@@ -857,6 +930,9 @@
     }
 
     public void destroy() {
+        if (mDestroyed) {
+            return;
+        }
         // We should not keep these bitmaps around because the Call objects may be held for logging
         // purposes.
         // TODO: Make a container object that only stores the information we care about for Logging.
@@ -867,6 +943,7 @@
         closeRttStreams();
 
         Log.addEvent(this, LogUtils.Events.DESTROYED);
+        mDestroyed = true;
     }
 
     private void closeRttStreams() {
@@ -927,6 +1004,9 @@
         s.append(SimpleDateFormat.getDateTimeInstance().format(new Date(getCreationTimeMillis())));
         s.append("]");
         s.append(isIncoming() ? "(MT - incoming)" : "(MO - outgoing)");
+        s.append("(User=");
+        s.append(getAssociatedUser());
+        s.append(")");
         s.append("\n\t");
 
         PhoneAccountHandle targetPhoneAccountHandle = getTargetPhoneAccount();
@@ -1038,10 +1118,9 @@
 
     @Override
     public ConnectionServiceFocusManager.ConnectionServiceFocus getConnectionServiceWrapper() {
-        return mConnectionService;
+        return (!mIsTransactionalCall ? mConnectionService : mTransactionalService);
     }
 
-    @VisibleForTesting
     public int getState() {
         return mState;
     }
@@ -1248,10 +1327,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 +1356,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 +1506,10 @@
         }
     }
 
+    public Uri getContactPhotoUri() {
+        return mCallerInfo != null ? mCallerInfo.getContactDisplayPhotoUri() : null;
+    }
+
     public String getCallerDisplayName() {
         return mCallerDisplayName;
     }
@@ -1420,6 +1529,12 @@
         }
     }
 
+    void setContactPhotoUri(Uri contactPhotoUri) {
+        if (mCallerInfo != null) {
+            mCallerInfo.SetContactDisplayPhotoUri(contactPhotoUri);
+        }
+    }
+
     public String getName() {
         return mCallerInfo == null ? null : mCallerInfo.getName();
     }
@@ -1472,12 +1587,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.
@@ -1487,6 +1610,21 @@
     }
 
     /**
+     * @return {@code true} if the target phone account is in ECBM.
+     */
+    public boolean isInECBM() {
+        return mIsInECBM;
+    }
+
+    /**
+     * Set if the target phone account is in ECBM.
+     * @param isInEcbm {@code true} if target phone account is in ECBM, {@code false} otherwise.
+     */
+    public void setIsInECBM(boolean isInECBM) {
+        mIsInECBM = isInECBM;
+    }
+
+    /**
      * @return {@code true} if the network has identified this call as an emergency call.
      */
     public boolean isNetworkIdentifiedEmergencyCall() {
@@ -1577,6 +1715,11 @@
     public void setTargetPhoneAccount(PhoneAccountHandle accountHandle) {
         if (!Objects.equals(mTargetPhoneAccountHandle, accountHandle)) {
             mTargetPhoneAccountHandle = accountHandle;
+            // Update the last MO emergency call in the helper, if applicable.
+            if (isEmergencyCall() && !isIncoming()) {
+                mCallsManager.getEmergencyCallHelper().setLastOutgoingEmergencyCallPAH(
+                        accountHandle);
+            }
             for (Listener l : mListeners) {
                 l.onTargetPhoneAccountChanged(this);
             }
@@ -1584,6 +1727,30 @@
         }
         checkIfVideoCapable();
         checkIfRttCapable();
+
+        if (accountHandle != null) {
+            mCallStateChangedAtomWriter.setUid(
+                    accountHandle.getComponentName().getPackageName(),
+                    mContext.getPackageManager());
+            // Set the associated user for the call for MT calls based on the target phone account.
+            if (isIncoming() && !accountHandle.getUserHandle().equals(mAssociatedUser)) {
+                setAssociatedUser(accountHandle.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 +1883,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;
     }
@@ -1786,7 +1983,7 @@
         if (phoneAccount != null) {
             final UserHandle userHandle;
             if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
-                userHandle = mInitiatingUser;
+                userHandle = mAssociatedUser;
             } else {
                 userHandle = mTargetPhoneAccountHandle.getUserHandle();
             }
@@ -1897,8 +2094,12 @@
         return mCreationTimeMillis;
     }
 
-    public void setCreationTimeMillis(long time) {
-        mCreationTimeMillis = time;
+    /**
+     * @return The elapsed realtime millis when the call was created; ONLY useful for determining
+     * how long has elapsed since the call was first created.
+     */
+    public long getCreationElapsedRealtimeMillis() {
+        return mCreationElapsedRealtimeMillis;
     }
 
     public long getConnectTimeMillis() {
@@ -1993,8 +2194,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 +2298,6 @@
         return mConferenceLevelActiveCall;
     }
 
-    @VisibleForTesting
     public ConnectionServiceWrapper getConnectionService() {
         return mConnectionService;
     }
@@ -2192,6 +2394,7 @@
             CallIdMapper idMapper,
             ParcelableConference conference) {
         Log.v(this, "handleCreateConferenceSuccessful %s", conference);
+        mIsCreateConnectionComplete = true;
         setTargetPhoneAccount(conference.getPhoneAccount());
         setHandle(conference.getHandle(), conference.getHandlePresentation());
 
@@ -2201,7 +2404,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 +2428,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 +2442,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 +2476,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 +2498,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 +2579,6 @@
         disconnect(0);
     }
 
-    @VisibleForTesting
     public void disconnect(String reason) {
         disconnect(0, reason);
     }
@@ -2400,7 +2607,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 +2618,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 +2690,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 +2783,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 +2797,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 +2821,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 +2834,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 +2856,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 +2878,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 +2900,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 +2922,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 +2949,6 @@
         }
     }
 
-    @VisibleForTesting
     public boolean isActive() {
         return mState == CallState.ACTIVE;
     }
@@ -2729,18 +2959,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 +3007,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 +3041,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 +3079,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 +3135,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 +3146,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 +3157,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 +3169,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 +3182,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 +3210,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 +3240,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 +3292,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 +3315,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 +3324,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 +3347,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 +3356,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 +3662,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 +3735,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 +3748,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 +3776,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 +3804,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 +3962,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 +3992,10 @@
         return mCallDirection == CALL_DIRECTION_UNKNOWN;
     }
 
+    public boolean isOutgoing() {
+        return mCallDirection == CALL_DIRECTION_OUTGOING;
+    }
+
     /**
      * Determines if this call is in a disconnecting state.
      *
@@ -3697,19 +4015,26 @@
     }
 
     /**
-     * @return user handle of user initiating the outgoing call.
+     * It's possible that the target phone account isn't set when a user hasn't selected a
+     * default sim to place a call. Instead of using the user from the target phone account to
+     * associate the user with a call, we'll use mAssociatedUser instead. For MT calls, we will
+     * continue to use the target phone account user (as it's always set) and for MO calls, we will
+     * use the initiating user instead.
+     *
+     * @return user handle of user associated with the call.
      */
-    public UserHandle getInitiatingUser() {
-        return mInitiatingUser;
+    public UserHandle getAssociatedUser() {
+        return mAssociatedUser;
     }
 
     /**
-     * Set the user handle of user initiating the outgoing call.
-     * @param initiatingUser
+     * Set the user handle of user associated with the call.
+     * @param associatedUser
      */
-    public void setInitiatingUser(UserHandle initiatingUser) {
-        Preconditions.checkNotNull(initiatingUser);
-        mInitiatingUser = initiatingUser;
+    public void setAssociatedUser(UserHandle associatedUser) {
+        Log.i(this, "Setting associated user for call");
+        Preconditions.checkNotNull(associatedUser);
+        mAssociatedUser = associatedUser;
     }
 
     static int getStateFromConnectionState(int state) {
@@ -3772,7 +4097,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.
     }
 
     /**
@@ -3803,6 +4129,15 @@
      * @param extras The extras.
      */
     public void onConnectionEvent(String event, Bundle extras) {
+        if (mIsTransactionalCall) {
+            // send the Event directly to the ICS via the InCallController listener
+            for (Listener l : mListeners) {
+                l.onConnectionEvent(this, event, extras);
+            }
+            // Don't run the below block since it applies to Calls that are attached to a
+            // ConnectionService
+            return;
+        }
         // Don't log call quality reports; they're quite frequent and will clog the log.
         if (!Connection.EVENT_CALL_QUALITY_REPORT.equals(event)) {
             Log.addEvent(this, LogUtils.Events.CONNECTION_EVENT, event);
@@ -3847,6 +4182,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 +4503,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 +4602,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");
+        }
+        Log.addEvent(this, LogUtils.Events.START_STREAMING);
+        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;
+            }
+            Log.addEvent(this, LogUtils.Events.STOP_STREAMING);
+            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..ff76b9e 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);
@@ -141,6 +146,9 @@
 
     @Override
     public void onCallRemoved(Call call) {
+        if (mStreamingCall == call) {
+            mStreamingCall = null;
+        }
         if (shouldIgnoreCallForAudio(call)) {
             return; // Don't do audio handling for calls in a conference, or external calls.
         }
@@ -219,6 +227,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 +448,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 +489,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.getAssociatedUser();
+                // 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 +795,7 @@
                 .setHasHoldingCalls(mHoldingCalls.size() > 0)
                 .setHasAudioProcessingCalls(mAudioProcessingCalls.size() > 0)
                 .setIsTonePlaying(mIsTonePlaying)
+                .setIsStreaming((mStreamingCall != null) && (!mStreamingCall.isDisconnected()))
                 .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..9ad9094 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,82 @@
                     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");
+            mLocalLog.log("Enter Streaming");
+            mLocalLog.log("Mode MODE_COMMUNICATION_REDIRECT");
+            mAudioManager.setMode(AudioManager.MODE_COMMUNICATION_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:
+                    // Switch to either ringing, holding, or inactive
+                    transitionTo(calculateProperStateFromArgs(args));
+                    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 +809,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 +848,7 @@
         addState(mSimCallFocusState);
         addState(mVoipCallFocusState);
         addState(mAudioProcessingFocusState);
+        addState(mStreamingFocusState);
         addState(mOtherFocusState);
         setInitialState(mUnfocusedState);
         start();
@@ -747,6 +858,7 @@
                 .setHasHoldingCalls(false)
                 .setIsTonePlaying(false)
                 .setForegroundCallIsVoip(false)
+                .setIsStreaming(false)
                 .setSession(Log.createSubsession())
                 .build());
     }
@@ -800,12 +912,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..c5423c2 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) {
@@ -1539,15 +1629,23 @@
         mAvailableRoutes = mDeviceSupportedRoutes & getCurrentCallSupportedRoutes();
         mIsMuted = initState.isMuted();
         mWasOnSpeaker = false;
-        mContext.registerReceiver(mMuteChangeReceiver,
-                new IntentFilter(AudioManager.ACTION_MICROPHONE_MUTE_CHANGED));
-        mContext.registerReceiver(mMuteChangeReceiver,
-                new IntentFilter(AudioManager.STREAM_MUTE_CHANGED_ACTION));
-        mContext.registerReceiver(mSpeakerPhoneChangeReceiver,
-                new IntentFilter(AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED));
+        IntentFilter micMuteChangedFilter = new IntentFilter(
+                AudioManager.ACTION_MICROPHONE_MUTE_CHANGED);
+        micMuteChangedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+        mContext.registerReceiver(mMuteChangeReceiver, micMuteChangedFilter);
+
+        IntentFilter muteChangedFilter = new IntentFilter(AudioManager.STREAM_MUTE_CHANGED_ACTION);
+        muteChangedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+        mContext.registerReceiver(mMuteChangeReceiver, muteChangedFilter);
+
+        IntentFilter speakerChangedFilter = new IntentFilter(
+                AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED);
+        speakerChangedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+        mContext.registerReceiver(mSpeakerPhoneChangeReceiver, speakerChangedFilter);
 
         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 +1738,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 +1868,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..7e11b47
--- /dev/null
+++ b/src/com/android/server/telecom/CallEndpointController.java
@@ -0,0 +1,399 @@
+/*
+ * 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 (isCurrentEndpointRequestedEndpoint(route, bluetoothAddress)) {
+            Log.d(this, "requestCallEndpointChange: requested endpoint is already active");
+            callback.send(CallEndpoint.ENDPOINT_OPERATION_SUCCESS, new Bundle());
+            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);
+    }
+
+    public boolean isCurrentEndpointRequestedEndpoint(int requestedRoute, String requestedAddress) {
+        if (mCallsManager.getCallAudioManager() == null
+                || mCallsManager.getCallAudioManager().getCallAudioState() == null) {
+            return false;
+        }
+        CallAudioState currentAudioState = mCallsManager.getCallAudioManager().getCallAudioState();
+        // requested non-bt endpoint is already active
+        if (requestedRoute != CallAudioState.ROUTE_BLUETOOTH &&
+                requestedRoute == currentAudioState.getRoute()) {
+            return true;
+        }
+        // requested bt endpoint is already active
+        if (requestedRoute == CallAudioState.ROUTE_BLUETOOTH &&
+                currentAudioState.getActiveBluetoothDevice() != null &&
+                requestedAddress.equals(
+                        currentAudioState.getActiveBluetoothDevice().getAddress())) {
+            return true;
+        }
+        return false;
+    }
+
+    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) {
+                            String deviceName = device.getName();
+                            endpoint = new CallEndpoint(
+                                    deviceName != null ? deviceName : "",
+                                    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/CallIntentProcessor.java b/src/com/android/server/telecom/CallIntentProcessor.java
index 7f864b8..7953324 100644
--- a/src/com/android/server/telecom/CallIntentProcessor.java
+++ b/src/com/android/server/telecom/CallIntentProcessor.java
@@ -182,9 +182,10 @@
         boolean isPrivilegedDialer = defaultDialerCache.isDefaultOrSystemDialer(callingPackage,
                 initiatingUser.getIdentifier());
 
+
         NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster(
                 context, callsManager, intent, callsManager.getPhoneNumberUtilsAdapter(),
-                isPrivilegedDialer, defaultDialerCache);
+                isPrivilegedDialer, defaultDialerCache, new MmiUtils());
 
         // If the broadcaster comes back with an immediate error, disconnect and show a dialog.
         NewOutgoingCallIntentBroadcaster.CallDisposition disposition = broadcaster.evaluateCall();
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..3005656
--- a/src/com/android/server/telecom/CallLogManager.java
+++ b/src/com/android/server/telecom/CallLogManager.java
@@ -324,7 +324,7 @@
         }
 
         PhoneAccount phoneAccount = mPhoneAccountRegistrar.getPhoneAccountUnchecked(accountHandle);
-        UserHandle initiatingUser = call.getInitiatingUser();
+        UserHandle initiatingUser = call.getAssociatedUser();
         if (phoneAccount != null &&
                 phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER)) {
             if (initiatingUser != null &&
@@ -386,7 +386,12 @@
         if (okayToLog) {
             AddCallArgs args = new AddCallArgs(mContext, paramBuilder.build(),
                     logCallCompletedListener);
+            Log.addEvent(call, LogUtils.Events.LOG_CALL, "number=" + Log.piiHandle(logNumber)
+                    + ",postDial=" + Log.piiHandle(call.getPostDialDigits()) + ",pres="
+                    + call.getHandlePresentation());
             logCallAsync(args);
+        } else {
+            Log.addEvent(call, LogUtils.Events.SKIP_CALL_LOG);
         }
     }
 
diff --git a/src/com/android/server/telecom/CallScreeningServiceHelper.java b/src/com/android/server/telecom/CallScreeningServiceHelper.java
index 0168590..9426100 100644
--- a/src/com/android/server/telecom/CallScreeningServiceHelper.java
+++ b/src/com/android/server/telecom/CallScreeningServiceHelper.java
@@ -229,8 +229,9 @@
                 serviceConnection,
                 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
                 | Context.BIND_SCHEDULE_LIKE_TOP_APP,
-                UserHandle.CURRENT)) {
-            Log.d(TAG, "bindService, found service, waiting for it to connect");
+                userHandle)) {
+            Log.d(TAG,"bindServiceAsUser, found service,"
+                    + "waiting for it to connect to user: %s", userHandle);
             return true;
         }
 
diff --git a/src/com/android/server/telecom/CallState.java b/src/com/android/server/telecom/CallState.java
index 0411ecc..c35c7ec 100644
--- a/src/com/android/server/telecom/CallState.java
+++ b/src/com/android/server/telecom/CallState.java
@@ -132,6 +132,46 @@
      */
     public static final int SIMULATED_RINGING = TelecomProtoEnums.SIMULATED_RINGING; // = 13
 
+    /**
+     * Determines if a given call state is "transitory".  A transitory call state is one which a
+     * call should only be in for a short duration of time before lower levels move it to a more
+     * permanent stable state.
+     *
+     * It is tempting to consider {@link #DIALING}, for example, to be transitory, however the time
+     * spent in this state is entirely dependent on how long the call is actually in that state and
+     * it is expected a call can be {@link #DIALING} for potentially a minute or more.
+     * @param callState the state to check
+     * @return {@code true} if the state is transitory, {@code false} otherwise.
+     */
+    public static boolean isTransitoryState(int callState) {
+        switch (callState) {
+            case NEW:
+            case CONNECTING:
+            case DISCONNECTING:
+            case ANSWERED:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Determines if a given call state is "intermediate".  An intermediate call state may exist
+     * before a stable call state, but may be longer than a transitory state.
+     *
+     * @param callState the state to check
+     * @return {@code true} if the state is intermediate, {@code false} otherwise.
+     */
+    public static boolean isIntermediateState(int callState) {
+        switch (callState) {
+            case DIALING:
+            case RINGING:
+                return true;
+            default:
+                return false;
+        }
+    }
+
     public static String toString(int callState) {
         switch (callState) {
             case NEW:
diff --git a/src/com/android/server/telecom/CallStreamingController.java b/src/com/android/server/telecom/CallStreamingController.java
new file mode 100644
index 0000000..1323633
--- /dev/null
+++ b/src/com/android/server/telecom/CallStreamingController.java
@@ -0,0 +1,443 @@
+/*
+ * 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.telecom.Log;
+
+import com.android.internal.telecom.ICallStreamingService;
+import com.android.server.telecom.voip.ParallelTransaction;
+import com.android.server.telecom.voip.SerialTransaction;
+import com.android.server.telecom.voip.VoipCallTransaction;
+import com.android.server.telecom.voip.VoipCallTransactionResult;
+
+import java.util.ArrayList;
+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) {
+            Log.i(this, "onConnectedInternal: callid=%s", call.getId());
+            Bundle extras = new Bundle();
+            extras.putString(StreamingCall.EXTRA_CALL_ID, call.getId());
+            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.getHandle(), extras));
+            mIsStreaming = true;
+        }
+    }
+
+    private void resetController() {
+        synchronized (mLock) {
+            mStreamingCall = null;
+            mTransactionalServiceWrapper = null;
+            if (mConnection != null) {
+                // Notify service streaming stopped and then unbind.
+                try {
+                    mService.onCallStreamingStopped();
+                } catch (RemoteException e) {
+                    // Could not notify stop streaming; we're about to just unbind so this is
+                    // unfortunate but not the end of the world.
+                    Log.e(this, e, "resetController: failed to notify stop streaming.");
+                }
+                mContext.unbindService(mConnection);
+                mConnection = null;
+            }
+            mService = null;
+            mIsStreaming = false;
+        }
+    }
+
+    public boolean isStreaming() {
+        synchronized (mLock) {
+            return mIsStreaming;
+        }
+    }
+
+    public static class QueryCallStreamingTransaction extends VoipCallTransaction {
+        private final CallsManager mCallsManager;
+
+        public QueryCallStreamingTransaction(CallsManager callsManager) {
+            super(callsManager.getLock());
+            mCallsManager = callsManager;
+        }
+
+        @Override
+        public CompletableFuture<VoipCallTransactionResult> processTransaction(Void v) {
+            Log.i(this, "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 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.i(this, "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 {
+        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.getAssociatedUser();
+            mContext = context;
+        }
+
+        @SuppressLint("LongLogTag")
+        @Override
+        public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+            Log.i(this, "processTransaction");
+            CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
+            RoleManager roleManager = mContext.getSystemService(RoleManager.class);
+            PackageManager packageManager = mContext.getPackageManager();
+            if (roleManager == null || packageManager == null) {
+                Log.w(this, "processTransaction: 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.w(this, "processTransaction: Can't find streaming app");
+                future.complete(new VoipCallTransactionResult(
+                        VoipCallTransactionResult.RESULT_FAILED, MESSAGE));
+                return future;
+            }
+            Log.i(this, "processTransaction: servicePackage=%s", holders.get(0));
+            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.w(this, "processTransaction: 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)) {
+                Log.w(this, "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.w(this, "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 {
+        public UnbindStreamingServiceTransaction() {
+            super(mTelecomLock);
+        }
+
+        @SuppressLint("LongLogTag")
+        @Override
+        public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+            Log.i(this, "processTransaction (unbindStreaming");
+            CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
+
+            resetController();
+            future.complete(new VoipCallTransactionResult(VoipCallTransactionResult.RESULT_SUCCEED,
+                    null));
+            return future;
+        }
+    }
+
+    public class StartStreamingTransaction extends SerialTransaction {
+        private Call mCall;
+
+        public StartStreamingTransaction(List<VoipCallTransaction> subTransactions, Call call,
+                TelecomSystem.SyncRoot lock) {
+            super(subTransactions, lock);
+            mCall = call;
+        }
+
+        @Override
+        public void handleTransactionFailure() {
+            mTransactionalServiceWrapper.stopCallStreaming(mCall);
+        }
+    }
+
+    public VoipCallTransaction getStartStreamingTransaction(CallsManager callsManager,
+            TransactionalServiceWrapper wrapper, Call call, TelecomSystem.SyncRoot lock) {
+        // start streaming transaction flow:
+        //     1. make sure there's no ongoing streaming call --> bind to EXO
+        //     2. change audio mode
+        //     3. bind to EXO
+        // If bind to EXO failed, add transaction for stop the streaming
+
+        // create list for multiple transactions
+        List<VoipCallTransaction> transactions = new ArrayList<>();
+        transactions.add(new QueryCallStreamingTransaction(callsManager));
+        transactions.add(new AudioInterceptionTransaction(call, true, lock));
+        transactions.add(getCallStreamingServiceTransaction(
+                callsManager.getContext(), wrapper, call));
+        return new StartStreamingTransaction(transactions, call, lock);
+    }
+
+    public VoipCallTransaction getStopStreamingTransaction(Call call, TelecomSystem.SyncRoot lock) {
+        // TODO: implement this
+        // Stop streaming transaction flow:
+        List<VoipCallTransaction> transactions = new ArrayList<>();
+
+        // 1. unbind to call streaming service
+        transactions.add(getUnbindStreamingServiceTransaction());
+        // 2. audio route operations
+        transactions.add(new CallStreamingController.AudioInterceptionTransaction(call,
+                false, lock));
+        return new ParallelTransaction(transactions, lock);
+    }
+
+    @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);
+                    break;
+                case CallState.DISCONNECTING:
+                case CallState.DISCONNECTED:
+                    Log.addEvent(call, LogUtils.Events.STOP_STREAMING);
+                    transaction = new CallStreamingStateChangeTransaction(
+                            StreamingCall.STATE_DISCONNECTED);
+                    break;
+                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(this, exception, "Exception when set call "
+                                        + "streaming state to streaming app");
+                            }
+                        });
+            }
+        }
+    }
+
+    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 {
+                Log.i(this, "onServiceConnected: " + name);
+                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() {
+            resetController();
+            if (!mFuture.isDone()) {
+                mFuture.complete(new VoipCallTransactionResult(
+                        VoipCallTransactionResult.RESULT_FAILED,
+                        "STREAMING_FAILED_SENDER_BINDING_ERROR"));
+            } else {
+                mWrapper.onCallStreamingFailed(mCall, STREAMING_FAILED_SENDER_BINDING_ERROR);
+            }
+        }
+    }
+}
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
old mode 100755
new mode 100644
index 3fce799..4d2f273
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -16,9 +16,15 @@
 
 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_NOT_RUNNING;
 import static android.provider.CallLog.Calls.USER_MISSED_NO_ANSWER;
 import static android.provider.CallLog.Calls.USER_MISSED_SHORT_RING;
 import static android.telecom.TelecomManager.ACTION_POST_CALL;
@@ -32,17 +38,15 @@
 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.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.AlertDialog;
 import android.app.KeyguardManager;
+import android.app.NotificationManager;
+import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
@@ -50,6 +54,8 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.ResolveInfoFlags;
+import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
 import android.graphics.Color;
 import android.graphics.drawable.ColorDrawable;
@@ -58,13 +64,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;
@@ -75,7 +82,10 @@
 import android.provider.CallLog.Calls;
 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 +103,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;
@@ -103,27 +115,33 @@
 import android.widget.Button;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IntentForwarderActivity;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.telecom.bluetooth.BluetoothRouteManager;
 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.CallStreamingNotification;
 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 +156,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 +175,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 +198,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 +209,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 +257,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};
@@ -284,6 +353,16 @@
             new ConcurrentHashMap<Call, Boolean>(8, 0.9f, 1));
 
     /**
+     * List of self-managed calls that have been initialized but not yet added to
+     * CallsManager#addCall(Call). There is a window of time when a Call has been added to Telecom
+     * (e.g. TelecomManager#addNewIncomingCall) to actually added in CallsManager#addCall(Call).
+     * This list is helpful for the NotificationManagerService to know that Telecom is currently
+     * setting up a call which is an important set in making notifications non-dismissible.
+     */
+    private final Set<Call> mSelfManagedCallsBeingSetup = Collections.newSetFromMap(
+            new ConcurrentHashMap<Call, Boolean>(8, 0.9f, 1));
+
+    /**
      * A pending call is one which requires user-intervention in order to be placed.
      * Used by {@link #startCallConfirmation}.
      */
@@ -374,6 +453,16 @@
     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 UserManager mUserManager;
+    private final CallStreamingNotification mCallStreamingNotification;
 
     private final ConnectionServiceFocusManager.CallsManagerRequester mRequester =
             new ConnectionServiceFocusManager.CallsManagerRequester() {
@@ -394,14 +483,18 @@
 
     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();
+
+    private final MmiUtils mMmiUtils = new MmiUtils();
     /**
      * Listener to PhoneAccountRegistrar events.
      */
@@ -432,34 +525,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 +567,16 @@
             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,
+            CallStreamingNotification callStreamingNotification) {
+
         mContext = context;
         mLock = lock;
         mPhoneNumberUtilsAdapter = phoneNumberUtilsAdapter;
@@ -511,6 +593,7 @@
         mTimeoutsAdapter = timeoutsAdapter;
         mEmergencyCallHelper = emergencyCallHelper;
         mCallerInfoLookupHelper = callerInfoLookupHelper;
+        mEmergencyCallDiagnosticLogger = emergencyCallDiagnosticLogger;
 
         mDtmfLocalTonePlayer =
                 new DtmfLocalTonePlayer(new DtmfLocalTonePlayer.ToneGeneratorProxy());
@@ -522,7 +605,8 @@
                         wiredHeadsetManager,
                         statusBarNotifier,
                         audioServiceFactory,
-                        CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT
+                        CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT,
+                        asyncTaskExecutor
                 );
         callAudioRouteStateMachine.initialize();
 
@@ -532,7 +616,6 @@
                         bluetoothManager,
                         wiredHeadsetManager,
                         mDockManager);
-
         AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
         InCallTonePlayer.MediaPlayerFactory mediaPlayerFactory =
                 (resourceId, attributes) ->
@@ -549,11 +632,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 +660,17 @@
         mClockProxy = clockProxy;
         mToastFactory = toastFactory;
         mRoleManagerAdapter = roleManagerAdapter;
+        mTransactionManager = transactionManager;
+        mBlockedNumbersAdapter = blockedNumbersAdapter;
+        mCallStreamingController = new CallStreamingController(mContext, mLock);
+        mVoipCallMonitor = new VoipCallMonitor(mContext, mLock);
+        mCallStreamingNotification = callStreamingNotification;
 
         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 +679,16 @@
         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);
+        mListeners.add(mCallStreamingNotification);
+
+        mVoipCallMonitor.startMonitor();
 
         // There is no USER_SWITCHED broadcast for user 0, handle it here explicitly.
         final UserManager userManager = UserManager.get(mContext);
@@ -600,9 +699,14 @@
         // Register BroadcastReceiver to handle enhanced call blocking feature related event.
         IntentFilter intentFilter = new IntentFilter(
                 CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+        intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
         intentFilter.addAction(SystemContract.ACTION_BLOCK_SUPPRESSION_STATE_CHANGED);
         context.registerReceiver(mReceiver, intentFilter, Context.RECEIVER_EXPORTED);
         mGraphHandlerThreads = new LinkedList<>();
+
+        mCallAnomalyWatchdog = callAnomalyWatchdog;
+        mAsyncTaskExecutor = asyncTaskExecutor;
+        mUserManager = mContext.getSystemService(UserManager.class);
     }
 
     public void setIncomingCallNotifier(IncomingCallNotifier incomingCallNotifier) {
@@ -638,9 +742,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.getAssociatedUser()));
 
         setCallState(call, callState, "successful outgoing call");
         if (!mCalls.contains(call)) {
@@ -659,8 +765,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 +778,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 +810,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.getAssociatedUser();
+        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 +825,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 +843,7 @@
                     mContext, this, appLabelProxy, converter);
         }
         graph.addFilter(voicemailFilter);
+        graph.addFilter(dndCallFilter);
         graph.addFilter(blockCheckerFilter);
         graph.addFilter(carrierCallScreeningServiceFilter);
         graph.addFilter(callScreeningServiceFilter);
@@ -774,6 +891,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 +911,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 +931,9 @@
 
         if (result.shouldAllowCall) {
             incomingCall.setPostCallPackageName(
-                    getRoleManagerAdapter().getDefaultCallScreeningApp());
+                    getRoleManagerAdapter().getDefaultCallScreeningApp(
+                            incomingCall.getAssociatedUser()
+                    ));
 
             Log.i(this, "onCallFilteringComplete: allow call.");
             if (hasMaximumManagedRingingCalls(incomingCall)) {
@@ -861,7 +982,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 +1023,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 +1251,6 @@
         }
     }
 
-    @VisibleForTesting
     public Call getForegroundCall() {
         if (mCallAudioManager == null) {
             // Happens when getForegroundCall is called before full initialization.
@@ -1137,6 +1259,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 +1303,18 @@
         return mInCallController;
     }
 
+    public CallEndpointController getCallEndpointController() {
+        return mCallEndpointController;
+    }
+
     EmergencyCallHelper getEmergencyCallHelper() {
         return mEmergencyCallHelper;
     }
 
+    EmergencyCallDiagnosticLogger getEmergencyCallDiagnosticLogger() {
+        return mEmergencyCallDiagnosticLogger;
+    }
+
     public DefaultDialerCache getDefaultDialerCache() {
         return mDefaultDialerCache;
     }
@@ -1239,6 +1378,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 +1399,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);
@@ -1264,8 +1408,10 @@
             // Required for backwards compatibility
             handle = extras.getParcelable(TelephonyManager.EXTRA_INCOMING_NUMBER);
         }
+        PhoneAccount phoneAccount = mPhoneAccountRegistrar.getPhoneAccountUnchecked(
+                phoneAccountHandle);
         Call call = new Call(
-                getNextCallId(),
+                generateNextCallId(extras),
                 mContext,
                 this,
                 mLock,
@@ -1280,16 +1426,37 @@
                 isConference, /* isConference */
                 mClockProxy,
                 mToastFactory);
-
-        // Ensure new calls related to self-managed calls/connections are set as such.  This will
+        // 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.
-        PhoneAccount phoneAccount = mPhoneAccountRegistrar.getPhoneAccountUnchecked(
-                phoneAccountHandle);
+        boolean isSelfManaged = phoneAccount != null && phoneAccount.isSelfManaged();
+        call.setIsSelfManaged(isSelfManaged);
+        // It's important to start tracking self-managed calls as soon as the Call object is
+        // initialized so NotificationManagerService is aware Telecom is setting up a call
+        if (isSelfManaged) mSelfManagedCallsBeingSetup.add(call);
+
+        // 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);
+            if (extras.containsKey(CallAttributes.DISPLAY_NAME_KEY)) {
+                CharSequence displayName = extras.getCharSequence(CallAttributes.DISPLAY_NAME_KEY);
+                if (!TextUtils.isEmpty(displayName)) {
+                    call.setCallerDisplayName(displayName.toString(),
+                            TelecomManager.PRESENTATION_ALLOWED);
+                }
+            }
+            // Incoming address was set via EXTRA_INCOMING_CALL_ADDRESS above.
+            call.setAssociatedUser(phoneAccountHandle.getUserHandle());
+        }
+
         if (phoneAccount != null) {
             Bundle phoneAccountExtras = phoneAccount.getExtras();
-            call.setIsSelfManaged(phoneAccount.isSelfManaged());
             if (call.isSelfManaged()) {
                 // Self managed calls will always be voip audio mode.
                 call.setIsVoipAudioMode(true);
@@ -1298,7 +1465,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 +1477,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 +1567,35 @@
             }
         }
 
-        if (!isHandoverAllowed || (call.isSelfManaged() && !isIncomingCallPermitted(call,
-                call.getTargetPhoneAccount()))) {
+        CallFailureCause startFailCause =
+                checkIncomingCallPermitted(call, call.getTargetPhoneAccount());
+        // Check if the target phone account is possibly in ECBM.
+        call.setIsInECBM(getEmergencyCallHelper()
+                .isLastOutgoingEmergencyCallPAH(call.getTargetPhoneAccount()));
+        if (mUserManager.isQuietModeEnabled(call.getAssociatedUser())
+                && !call.isEmergencyCall() && !call.isInECBM()) {
+            Log.d(TAG, "Rejecting non-emergency call because the owner %s is not running.",
+                    phoneAccountHandle.getUserHandle());
+            call.setMissedReason(USER_MISSED_NOT_RUNNING);
+            call.setStartFailCause(CallFailureCause.INVALID_USE);
+            if (isConference) {
+                notifyCreateConferenceFailed(phoneAccountHandle, call);
+            } else {
+                notifyCreateConnectionFailed(phoneAccountHandle, call);
+            }
+        }
+        else 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 +1604,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 +1612,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) {
@@ -1453,8 +1649,11 @@
                 mToastFactory);
         call.initAnalytics();
 
+        // For unknown calls, base the associated user off of the target phone account handle.
+        call.setAssociatedUser(phoneAccountHandle.getUserHandle());
         setIntentExtrasAndStartTime(call, extras);
         call.addListener(this);
+        notifyStartCreateConnection(call);
         call.startCreateConnection(mPhoneAccountRegistrar);
     }
 
@@ -1518,6 +1717,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,
@@ -1525,7 +1732,6 @@
         boolean isReusedCall;
         Uri handle = isConference ? Uri.parse("tel:conf-factory") : participants.get(0);
         Call call = reuseOutgoingCall(handle);
-
         PhoneAccount account =
                 mPhoneAccountRegistrar.getPhoneAccount(requestedAccountHandle, initiatingUser);
         Bundle phoneAccountExtra = account != null ? account.getExtras() : null;
@@ -1544,7 +1750,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,
@@ -1553,14 +1759,48 @@
                     isConference ? participants : null,
                     null /* gatewayInfo */,
                     null /* connectionManagerPhoneAccount */,
-                    null /* requestedAccountHandle */,
+                    requestedAccountHandle /* targetPhoneAccountHandle */,
                     Call.CALL_DIRECTION_OUTGOING /* callDirection */,
                     false /* forceAttachToExistingConnection */,
                     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);
+                if (extras.containsKey(CallAttributes.DISPLAY_NAME_KEY)) {
+                    CharSequence displayName = extras.getCharSequence(
+                            CallAttributes.DISPLAY_NAME_KEY);
+                    if (!TextUtils.isEmpty(displayName)) {
+                        call.setCallerDisplayName(displayName.toString(),
+                                TelecomManager.PRESENTATION_ALLOWED);
+                    }
+                }
+            }
+
             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
@@ -1573,11 +1813,14 @@
                         || phoneAccountExtra.getBoolean(
                                 PhoneAccount.EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE, true));
             }
-            call.setInitiatingUser(initiatingUser);
+            call.setAssociatedUser(initiatingUser);
             isReusedCall = false;
         } else {
             isReusedCall = true;
         }
+        // It's important to start tracking self-managed calls as soon as the Call object is
+        // initialized so NotificationManagerService is aware Telecom is setting up a call
+        if (isSelfManaged) mSelfManagedCallsBeingSetup.add(call);
 
         int videoState = VideoProfile.STATE_AUDIO_ONLY;
         if (extras != null) {
@@ -1631,6 +1874,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;
@@ -1670,7 +1925,7 @@
         CompletableFuture<Call> makeRoomForCall = setAccountHandle.thenComposeAsync(
                 potentialPhoneAccounts -> {
                     Log.i(CallsManager.this, "make room for outgoing call stage");
-                    if (isPotentialInCallMMICode(handle) && !isSelfManaged) {
+                    if (mMmiUtils.isPotentialInCallMMICode(handle) && !isSelfManaged) {
                         return CompletableFuture.completedFuture(finalCall);
                     }
                     // If a call is being reused, then it has already passed the
@@ -1703,6 +1958,7 @@
                             notifyCreateConnectionFailed(
                                     finalCall.getTargetPhoneAccount(), finalCall);
                         }
+                        finalCall.setStartFailCause(CallFailureCause.IN_EMERGENCY_CALL);
                         return CompletableFuture.completedFuture(null);
                     }
 
@@ -1761,9 +2017,34 @@
                                 return CompletableFuture.completedFuture(null);
                             }
                             if (accountSuggestions == null || accountSuggestions.isEmpty()) {
+                                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 +2091,8 @@
             dialerSelectPhoneAccountFuture.thenAcceptBothAsync(contactLookupFuture,
                     (callPhoneAccountHandlePair, uriCallerInfoPair) -> {
                         Call theCall = callPhoneAccountHandlePair.first;
+                        UserHandle userHandleForCallScreening = theCall.
+                                getAssociatedUser();
                         boolean isInContacts = uriCallerInfoPair.second != null
                                 && uriCallerInfoPair.second.contactExists;
                         Log.d(CallsManager.this, "outgoingCallIdStage: isInContacts=%s",
@@ -1820,10 +2103,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);
@@ -1881,7 +2166,7 @@
                     setIntentExtrasAndStartTime(callToUse, extras);
                     setCallSourceToAnalytics(callToUse, originalIntent);
 
-                    if (isPotentialMMICode(handle) && !isSelfManaged) {
+                    if (mMmiUtils.isPotentialMMICode(handle) && !isSelfManaged) {
                         // Do not add the call if it is a potential MMI code.
                         callToUse.addListener(this);
                     } else if (!mCalls.contains(callToUse)) {
@@ -1895,6 +2180,113 @@
         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 showSwitchToManagedProfileDialog(Uri callUri, UserHandle initiatingUser,
+            int managedProfileUserId) {
+        // Note that the ACTION_CALL intent will resolve to Telecomm's UserCallActivity
+        // even if there is no dialer. Hence we explicitly check for whether a default dialer
+        // exists instead of relying on ActivityNotFound when sending the call intent.
+        if (TextUtils.isEmpty(
+                mDefaultDialerCache.getDefaultDialerApplication(managedProfileUserId))) {
+            Log.i(
+                    this,
+                    "Work profile telephony: default dialer app missing, showing error dialog.");
+            return maybeShowErrorDialog(callUri, managedProfileUserId, initiatingUser);
+        }
+
+        UserManager userManager = mContext.getSystemService(UserManager.class);
+        if (userManager.isQuietModeEnabled(UserHandle.of(managedProfileUserId))) {
+            Log.i(
+                    this,
+                    "Work profile telephony: quiet mode enabled, showing error dialog");
+            return maybeShowErrorDialog(callUri, managedProfileUserId, initiatingUser);
+        }
+        Log.i(
+                this,
+                "Work profile telephony: show forwarding call to managed profile dialog");
+        return maybeRedirectToIntentForwarder(callUri, initiatingUser);
+    }
+
+    private boolean maybeRedirectToIntentForwarder(
+            Uri callUri,
+            UserHandle initiatingUser) {
+        // Note: This intent is selected to match the CALL_MANAGED_PROFILE filter in
+        // DefaultCrossProfileIntentFiltersUtils. This ensures that it is redirected to
+        // IntentForwarderActivity.
+        Intent forwardCallIntent = new Intent(Intent.ACTION_CALL, callUri);
+        forwardCallIntent.addCategory(Intent.CATEGORY_DEFAULT);
+        ResolveInfo resolveInfos =
+                mContext.getPackageManager()
+                        .resolveActivityAsUser(
+                                forwardCallIntent,
+                                ResolveInfoFlags.of(0),
+                                initiatingUser.getIdentifier());
+        // Check that the intent will actually open the resolver rather than looping to the personal
+        // profile. This should not happen due to the cross profile intent filters.
+        if (resolveInfos == null
+                || !resolveInfos
+                    .getComponentInfo()
+                    .getComponentName()
+                    .getShortClassName()
+                    .equals(IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE)) {
+            Log.w(
+                    this,
+                    "Work profile telephony: Intent would not resolve to forwarder activity.");
+            return false;
+        }
+
+        try {
+            mContext.startActivityAsUser(forwardCallIntent, initiatingUser);
+            return true;
+        } catch (ActivityNotFoundException e) {
+            Log.e(this, e, "Unable to start call intent for work telephony");
+            return false;
+        }
+    }
+
+    private boolean maybeShowErrorDialog(
+            Uri callUri,
+            int managedProfileUserId,
+            UserHandle initiatingUser) {
+        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)
+                .isEmpty()) {
+            return false;
+        }
+        try {
+            mContext.startActivityAsUser(showErrorIntent, initiatingUser);
+            return true;
+        } catch (ActivityNotFoundException e) {
+            Log.e(
+                    this, e,"Work profile telephony: Unable to show error dialog");
+            return false;
+        }
+    }
+
     public void startConference(List<Uri> participants, Bundle clientExtras, String callingPackage,
             UserHandle initiatingUser) {
 
@@ -1934,7 +2326,8 @@
     private void bindForOutgoingCallerId(Call theCall) {
         // Find the user chosen call screening app.
         String callScreeningApp =
-                mRoleManagerAdapter.getDefaultCallScreeningApp();
+                mRoleManagerAdapter.getDefaultCallScreeningApp(
+                        theCall.getAssociatedUser());
 
         CompletableFuture future =
                 new CallScreeningServiceHelper(mContext,
@@ -2090,27 +2483,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.getAssociatedUser();
             // 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 +2535,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;
@@ -2377,7 +2774,7 @@
         // Auto-enable speakerphone if the originating intent specified to do so, if the call
         // is a video call, of if using speaker when docked
         PhoneAccount account = mPhoneAccountRegistrar.getPhoneAccount(
-                call.getTargetPhoneAccount(), call.getInitiatingUser());
+                call.getTargetPhoneAccount(), call.getAssociatedUser());
         boolean allowVideo = false;
         if (account != null) {
             allowVideo = account.hasCapabilities(PhoneAccount.CAPABILITY_VIDEO_CALLING);
@@ -2420,12 +2817,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.getAssociatedUser(), false).isEmpty()) {
             // If there are no call capable accounts, disconnect the call.
             markCallAsDisconnected(call, new DisconnectCause(DisconnectCause.CANCELED,
                     "No registered PhoneAccounts"));
@@ -2456,6 +2867,10 @@
     public void answerCall(Call call, int videoState) {
         if (!mCalls.contains(call)) {
             Log.i(this, "Request to answer a non-existent call %s", call);
+        } else if (call.isTransactionalCall()) {
+            // InCallAdapter is requesting to answer the given transactioanl call. Must get an ack
+            // from the client via a transaction before answering.
+            call.answer(videoState);
         } else {
             // Hold or disconnect the active call and request call focus for the incoming call.
             Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
@@ -2849,7 +3264,7 @@
     }
 
     @Override
-    public void onExtrasChanged(Call c, int source, Bundle extras) {
+    public void onExtrasChanged(Call c, int source, Bundle extras, String requestingPackageName) {
         if (source != Call.SOURCE_CONNECTION_SERVICE) {
             return;
         }
@@ -2877,6 +3292,18 @@
         return constructPossiblePhoneAccounts(handle, user, isVideo, isEmergency, false);
     }
 
+    // Returns whether the device is capable of 2 simultaneous active voice calls on different subs.
+    private boolean isDsdaCallingPossible() {
+        try {
+            return getTelephonyManager().getMaxNumberOfSimultaneouslyActiveSims() > 1
+                    || getTelephonyManager().getPhoneCapability()
+                           .getMaxActiveVoiceSubscriptions() > 1;
+        } catch (Exception e) {
+            Log.w(this, "exception in isDsdaCallingPossible(): ", e);
+            return false;
+        }
+    }
+
     public List<PhoneAccountHandle> constructPossiblePhoneAccounts(Uri handle, UserHandle user,
             boolean isVideo, boolean isEmergency, boolean isConference) {
 
@@ -2891,14 +3318,14 @@
         List<PhoneAccountHandle> allAccounts =
                 mPhoneAccountRegistrar.getCallCapablePhoneAccounts(handle.getScheme(), false, user,
                         capabilities,
-                        isEmergency ? 0 : PhoneAccount.CAPABILITY_EMERGENCY_CALLS_ONLY);
-        if (mMaxNumberOfSimultaneouslyActiveSims < 0) {
-            mMaxNumberOfSimultaneouslyActiveSims =
-                    getTelephonyManager().getMaxNumberOfSimultaneouslyActiveSims();
-        }
+                        isEmergency ? 0 : PhoneAccount.CAPABILITY_EMERGENCY_CALLS_ONLY,
+                        isEmergency);
         // Only one SIM PhoneAccount can be active at one time for DSDS. Only that SIM PhoneAccount
         // should be available if a call is already active on the SIM account.
-        if (mMaxNumberOfSimultaneouslyActiveSims == 1) {
+        // Similarly, the emergency call should be attempted over the same PhoneAccount as the
+        // ongoing call. However, if the ongoing call is over cross-SIM registration, then the
+        // emergency call will be attempted over a different Phone object at a later stage.
+        if (isEmergency || !isDsdaCallingPossible()) {
             List<PhoneAccountHandle> simAccounts =
                     mPhoneAccountRegistrar.getSimPhoneAccountsOfCurrentUser();
             PhoneAccountHandle ongoingCallAccount = null;
@@ -2937,6 +3364,14 @@
         }
     }
 
+    @Override
+    public void onCallStreamingStateChanged(Call call, boolean isStreaming) {
+        Log.v(this, "onCallStreamingStateChanged: %b", isStreaming);
+        for (CallsManagerListener listener : mListeners) {
+            listener.onCallStreamingStateChanged(call, isStreaming);
+        }
+    }
+
     private void handleCallTechnologyChange(Call call) {
         if (call.getExtras() != null
                 && call.getExtras().containsKey(TelecomManager.EXTRA_CALL_TECHNOLOGY_TYPE)) {
@@ -2976,6 +3411,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,7 +3457,7 @@
         } else {
             if (setDefault) {
                 mPhoneAccountRegistrar
-                        .setUserSelectedOutgoingPhoneAccount(account, call.getInitiatingUser());
+                        .setUserSelectedOutgoingPhoneAccount(account, call.getAssociatedUser());
             }
 
             if (mPendingAccountSelection != null) {
@@ -3034,6 +3477,30 @@
         }
     }
 
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void updateCallEndpoint(CallEndpoint callEndpoint) {
+        Log.v(this, "updateCallEndpoint");
+        for (CallsManagerListener listener : mListeners) {
+            listener.onCallEndpointChanged(callEndpoint);
+        }
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void updateAvailableCallEndpoints(Set<CallEndpoint> availableCallEndpoints) {
+        Log.v(this, "updateAvailableCallEndpoints");
+        for (CallsManagerListener listener : mListeners) {
+            listener.onAvailableCallEndpointsChanged(availableCallEndpoints);
+        }
+    }
+
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public void updateMuteState(boolean isMuted) {
+        Log.v(this, "updateMuteState");
+        for (CallsManagerListener listener : mListeners) {
+            listener.onMuteStateChanged(isMuted);
+        }
+    }
+
     /**
      * Called when disconnect tone is started or stopped, including any InCallTone
      * after disconnected call.
@@ -3053,7 +3520,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);
@@ -3070,10 +3538,11 @@
      */
     boolean holdActiveCallForNewCall(Call call) {
         Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
-        Log.i(this, "holdActiveCallForNewCall, newCall: %s, activeCall: %s", call, activeCall);
+        Log.i(this, "holdActiveCallForNewCall, newCall: %s, activeCall: %s", call.getId(),
+                (activeCall == null ? "<none>" : activeCall.getId()));
         if (activeCall != null && activeCall != call) {
             if (canHold(activeCall)) {
-                activeCall.hold();
+                activeCall.hold("swap to " + call.getId());
                 return true;
             } else if (supportsHold(activeCall)
                     && areFromSameSource(activeCall, call)) {
@@ -3099,6 +3568,7 @@
                 Log.i(this, "holdActiveCallForNewCall: Holding active %s before making %s active.",
                         activeCall.getId(), call.getId());
                 activeCall.hold();
+                call.increaseHeldByThisCallCount();
                 return true;
             } else {
                 // This call does not support hold. If it is from a different connection
@@ -3124,6 +3594,45 @@
         return false;
     }
 
+    // attempt to hold the requested call and complete the callback on the result
+    public void transactionHoldPotentialActiveCallForNewCall(Call newCall,
+            OutcomeReceiver<Boolean, CallException> callback) {
+        Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
+        Log.i(this, "transactionHoldPotentialActiveCallForNewCall: "
+                + "newCall=[%s], activeCall=[%s]", newCall, activeCall);
+
+        // early exit if there is no need to hold an active call
+        if (activeCall == null || activeCall == newCall) {
+            Log.i(this, "transactionHoldPotentialActiveCallForNewCall:"
+                    + " no need to hold activeCall");
+            callback.onResult(true);
+            return;
+        }
+
+        // before attempting CallsManager#holdActiveCallForNewCall(Call), check if it'll fail early
+        if (!canHold(activeCall) &&
+                !(supportsHold(activeCall) && areFromSameSource(activeCall, newCall))) {
+            Log.i(this, "transactionHoldPotentialActiveCallForNewCall: "
+                    + "conditions show the call cannot be held.");
+            callback.onError(new CallException("call does not support hold",
+                    CallException.CODE_CANNOT_HOLD_CURRENT_ACTIVE_CALL));
+            return;
+        }
+
+        // attempt to hold the active call
+        if (!holdActiveCallForNewCall(newCall)) {
+            Log.i(this, "transactionHoldPotentialActiveCallForNewCall: "
+                    + "attempted to hold call but failed.");
+            callback.onError(new CallException("cannot hold active call failed",
+                    CallException.CODE_CANNOT_HOLD_CURRENT_ACTIVE_CALL));
+            return;
+        }
+
+        // officially mark the activeCall as held
+        markCallAsOnHold(activeCall);
+        callback.onResult(true);
+    }
+
     @VisibleForTesting
     public void markCallAsActive(Call call) {
         Log.i(this, "markCallAsActive, isSelfManaged: " + call.isSelfManaged());
@@ -3158,7 +3667,6 @@
         }
     }
 
-    @VisibleForTesting
     public void markCallAsOnHold(Call call) {
         setCallState(call, CallState.ON_HOLD, "on-hold set explicitly");
     }
@@ -3169,8 +3677,9 @@
      *
      * @param disconnectCause The disconnect cause, see {@link android.telecom.DisconnectCause}.
      */
-    @VisibleForTesting
     public void markCallAsDisconnected(Call call, DisconnectCause disconnectCause) {
+        Log.i(this, "markCallAsDisconnected: call=%s; disconnectCause=%s",
+                call.toString(), disconnectCause.toString());
         int oldState = call.getState();
         if (call.getState() == CallState.SIMULATED_RINGING
                 && disconnectCause.getCode() == DisconnectCause.REMOTE) {
@@ -3195,6 +3704,17 @@
             }
         }
 
+        // Notify listeners that the call was disconnected before being added to CallsManager.
+        // Listeners will not receive onAdded or onRemoved callbacks.
+        if (!mCalls.contains(call)) {
+            if (call.isEmergencyCall()) {
+                mAnomalyReporter.reportAnomaly(
+                        EMERGENCY_CALL_DISCONNECTED_BEFORE_BEING_ADDED_ERROR_UUID,
+                        EMERGENCY_CALL_DISCONNECTED_BEFORE_BEING_ADDED_ERROR_MSG);
+            }
+            mListeners.forEach(l -> l.onCreateConnectionFailed(call));
+        }
+
         // If a call diagnostic service is in use, we will log the original telephony-provided
         // disconnect cause, inform the CDS of the disconnection, and then chain the update of the
         // call state until AFTER the CDS reports it's result back.
@@ -3239,7 +3759,7 @@
     /**
      * Removes an existing disconnected call, and notifies the in-call app.
      */
-    void markCallAsRemoved(Call call) {
+    public void markCallAsRemoved(Call call) {
         if (call.isDisconnectHandledViaFuture()) {
             Log.i(this, "markCallAsRemoved; callid=%s, postingToFuture.", call.getId());
             // A future is being used due to a CallDiagnosticService handling the call.  We will
@@ -3264,38 +3784,57 @@
      * @param call The call.
      */
     private void performRemoval(Call call) {
-        mInCallController.getBindingFuture().thenRunAsync(() -> {
-            call.maybeCleanupHandover();
-            removeCall(call);
-            Call foregroundCall = mCallAudioManager.getPossiblyHeldForegroundCall();
-            if (mLocallyDisconnectingCalls.contains(call)) {
-                boolean isDisconnectingChildCall = call.isDisconnectingChildCall();
-                Log.v(this, "performRemoval: isDisconnectingChildCall = "
-                        + isDisconnectingChildCall + "call -> %s", call);
-                mLocallyDisconnectingCalls.remove(call);
-                // Auto-unhold the foreground call due to a locally disconnected call, except if the
-                // call which was disconnected is a member of a conference (don't want to auto
-                // un-hold the conference if we remove a member of the conference).
-                if (!isDisconnectingChildCall && foregroundCall != null
-                        && foregroundCall.getState() == CallState.ON_HOLD) {
-                    foregroundCall.unhold();
-                }
-            } else if (foregroundCall != null &&
-                    !foregroundCall.can(Connection.CAPABILITY_SUPPORT_HOLD) &&
-                    foregroundCall.getState() == CallState.ON_HOLD) {
+        if (mInCallController.getBindingFuture() != null) {
+            mInCallController.getBindingFuture().thenRunAsync(() -> {
+                        doRemoval(call);
+                    }, new LoggedHandlerExecutor(mHandler, "CM.pR", mLock))
+                    .exceptionally((throwable) -> {
+                        Log.e(TAG, throwable, "Error while executing call removal");
+                        mAnomalyReporter.reportAnomaly(CALL_REMOVAL_EXECUTION_ERROR_UUID,
+                                CALL_REMOVAL_EXECUTION_ERROR_MSG);
+                        return null;
+                    });
+        } else {
+            doRemoval(call);
+        }
+    }
 
-                // The new foreground call is on hold, however the carrier does not display the hold
-                // button in the UI.  Therefore, we need to auto unhold the held call since the user
-                // has no means of unholding it themselves.
-                Log.i(this, "performRemoval: Auto-unholding held foreground call (call doesn't "
-                        + "support hold)");
+    /**
+     * Code to perform removal of a call.  Called above from {@link #performRemoval(Call)} either
+     * async (in live code) or sync (in testing).
+     * @param call the call to remove.
+     */
+    private void doRemoval(Call call) {
+        call.maybeCleanupHandover();
+        removeCall(call);
+        Call foregroundCall = mCallAudioManager.getPossiblyHeldForegroundCall();
+        if (mLocallyDisconnectingCalls.contains(call)) {
+            boolean isDisconnectingChildCall = call.isDisconnectingChildCall();
+            Log.v(this, "performRemoval: isDisconnectingChildCall = "
+                    + isDisconnectingChildCall + "call -> %s", call);
+            mLocallyDisconnectingCalls.remove(call);
+            // Auto-unhold the foreground call due to a locally disconnected call, except if the
+            // call which was disconnected is a member of a conference (don't want to auto
+            // un-hold the conference if we remove a member of the conference).
+            // Also, ensure that the call we're removing is from the same ConnectionService as
+            // the one we're removing.  We don't want to auto-unhold between ConnectionService
+            // implementations, especially if one is managed and the other is a VoIP CS.
+            if (!isDisconnectingChildCall && foregroundCall != null
+                    && foregroundCall.getState() == CallState.ON_HOLD
+                    && areFromSameSource(foregroundCall, call)) {
                 foregroundCall.unhold();
             }
-        }, new LoggedHandlerExecutor(mHandler, "CM.pR", mLock))
-                .exceptionally((throwable) -> {
-                    Log.e(TAG, throwable, "Error while executing call removal");
-                    return null;
-                });
+        } else if (foregroundCall != null &&
+                !foregroundCall.can(Connection.CAPABILITY_SUPPORT_HOLD) &&
+                foregroundCall.getState() == CallState.ON_HOLD) {
+
+            // The new foreground call is on hold, however the carrier does not display the hold
+            // button in the UI.  Therefore, we need to auto unhold the held call since the user
+            // has no means of unholding it themselves.
+            Log.i(this, "performRemoval: Auto-unholding held foreground call (call doesn't "
+                    + "support hold)");
+            foregroundCall.unhold();
+        }
     }
 
     /**
@@ -3365,10 +3904,6 @@
         return false;
     }
 
-    boolean hasActiveOrHoldingCall() {
-        return getFirstCallWithState(CallState.ACTIVE, CallState.ON_HOLD) != null;
-    }
-
     boolean hasRingingCall() {
         return getFirstCallWithState(CallState.RINGING, CallState.ANSWERED) != null;
     }
@@ -3490,15 +4025,6 @@
         return getFirstCallWithState(CallState.ACTIVE);
     }
 
-    Call getDialingCall() {
-        return getFirstCallWithState(CallState.DIALING);
-    }
-
-    @VisibleForTesting
-    public Call getHeldCall() {
-        return getFirstCallWithState(CallState.ON_HOLD);
-    }
-
     public Call getHeldCallByConnectionService(PhoneAccountHandle targetPhoneAccount) {
         Optional<Call> heldCall = mCalls.stream()
                 .filter(call -> PhoneAccountHandle.areFromSamePackage(call.getTargetPhoneAccount(),
@@ -3622,6 +4148,11 @@
                 mClockProxy,
                 mToastFactory);
 
+        // Unlike connections, conferences are not created first and then notified as create
+        // connection complete from the CS.  They originate from the CS and are reported directly to
+        // telecom where they're added (see below).
+        call.setIsCreateConnectionComplete(true);
+
         setCallState(call, Call.getStateFromConnectionState(parcelableConference.getState()),
                 "new conference call");
         call.setHandle(parcelableConference.getHandle(),
@@ -3631,7 +4162,9 @@
         call.setVideoState(parcelableConference.getVideoState());
         call.setVideoProvider(parcelableConference.getVideoProvider());
         call.setStatusHints(parcelableConference.getStatusHints());
-        call.putExtras(Call.SOURCE_CONNECTION_SERVICE, parcelableConference.getExtras());
+        call.putConnectionServiceExtras(parcelableConference.getExtras());
+        // For conference calls, set the associated user from the target phone account user handle.
+        call.setAssociatedUser(phoneAccount.getUserHandle());
         // In case this Conference was added via a ConnectionManager, keep track of the original
         // Connection ID as created by the originating ConnectionService.
         Bundle extras = parcelableConference.getExtras();
@@ -3716,10 +4249,15 @@
      */
     @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);
         mCalls.add(call);
+        mSelfManagedCallsBeingSetup.remove(call);
 
         // Specifies the time telecom finished routing the call. This is used by the dialer for
         // analytics.
@@ -3748,6 +4286,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();
@@ -3758,6 +4301,7 @@
             mCalls.remove(call);
             shouldNotify = true;
         }
+        mSelfManagedCallsBeingSetup.remove(call);
 
         call.destroy();
         updateExternalCallCanPullSupport();
@@ -4008,37 +4552,6 @@
         }
     }
 
-    private boolean isPotentialMMICode(Uri handle) {
-        return (handle != null && handle.getSchemeSpecificPart() != null
-                && handle.getSchemeSpecificPart().contains("#"));
-    }
-
-    /**
-     * Determines if a dialed number is potentially an In-Call MMI code.  In-Call MMI codes are
-     * MMI codes which can be dialed when one or more calls are in progress.
-     * <P>
-     * Checks for numbers formatted similar to the MMI codes defined in:
-     * {@link com.android.internal.telephony.Phone#handleInCallMmiCommands(String)}
-     *
-     * @param handle The URI to call.
-     * @return {@code True} if the URI represents a number which could be an in-call MMI code.
-     */
-    private boolean isPotentialInCallMMICode(Uri handle) {
-        if (handle != null && handle.getSchemeSpecificPart() != null &&
-                handle.getScheme() != null &&
-                handle.getScheme().equals(PhoneAccount.SCHEME_TEL)) {
-
-            String dialedNumber = handle.getSchemeSpecificPart();
-            return (dialedNumber.equals("0") ||
-                    (dialedNumber.startsWith("1") && dialedNumber.length() <= 2) ||
-                    (dialedNumber.startsWith("2") && dialedNumber.length() <= 2) ||
-                    dialedNumber.equals("3") ||
-                    dialedNumber.equals("4") ||
-                    dialedNumber.equals("5"));
-        }
-        return false;
-    }
-
     /**
      * Determines if there are any ongoing self managed calls for the given package/user.
      * @param packageName The package name to check.
@@ -4046,8 +4559,10 @@
      * @return {@code true} if the app has ongoing calls, or {@code false} otherwise.
      */
     public boolean isInSelfManagedCall(String packageName, UserHandle userHandle) {
-        return mCalls.stream().anyMatch(
-                c -> c.isSelfManaged()
+        return mSelfManagedCallsBeingSetup.stream().anyMatch(c -> c.isSelfManaged()
+                && c.getTargetPhoneAccount().getComponentName().getPackageName().equals(packageName)
+                && c.getTargetPhoneAccount().getUserHandle().equals(userHandle)) ||
+                mCalls.stream().anyMatch(c -> c.isSelfManaged()
                 && c.getTargetPhoneAccount().getComponentName().getPackageName().equals(packageName)
                 && c.getTargetPhoneAccount().getUserHandle().equals(userHandle));
     }
@@ -4101,6 +4616,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);
@@ -4185,33 +4754,42 @@
     }
 
     /**
-     * Determines if there are any self-managed calls.
+     * Note: isInSelfManagedCall(packageName, UserHandle) should always be used in favor or this
+     * method. This method determines if there are any self-managed calls globally.
      * @return {@code true} if there are self-managed calls, {@code false} otherwise.
      */
+    @VisibleForTesting
     public boolean hasSelfManagedCalls() {
-        return mCalls.stream().filter(call -> call.isSelfManaged()).count() > 0;
+        return mSelfManagedCallsBeingSetup.size() > 0 ||
+                mCalls.stream().filter(call -> call.isSelfManaged()).count() > 0;
     }
 
     /**
      * Determines if there are any ongoing managed or self-managed calls.
      * Note: The {@link #ONGOING_CALL_STATES} are
+     * @param callingUser The user to scope the calls to.
+     * @param hasCrossUserAccess indicates if user has the INTERACT_ACROSS_USERS permission.
      * @return {@code true} if there are ongoing managed or self-managed calls, {@code false}
      *      otherwise.
      */
-    public boolean hasOngoingCalls() {
+    public boolean hasOngoingCalls(UserHandle callingUser, boolean hasCrossUserAccess) {
         return getNumCallsWithState(
                 CALL_FILTER_ALL, null /* excludeCall */,
+                callingUser, hasCrossUserAccess,
                 null /* phoneAccountHandle */,
                 ONGOING_CALL_STATES) > 0;
     }
 
     /**
      * Determines if there are any ongoing managed calls.
+     * @param callingUser The user to scope the calls to.
+     * @param hasCrossUserAccess indicates if user has the INTERACT_ACROSS_USERS permission.
      * @return {@code true} if there are ongoing managed calls, {@code false} otherwise.
      */
-    public boolean hasOngoingManagedCalls() {
+    public boolean hasOngoingManagedCalls(UserHandle callingUser, boolean hasCrossUserAccess) {
         return getNumCallsWithState(
                 CALL_FILTER_MANAGED, null /* excludeCall */,
+                callingUser, hasCrossUserAccess,
                 null /* phoneAccountHandle */,
                 ONGOING_CALL_STATES) > 0;
     }
@@ -4292,6 +4870,7 @@
             }
             //  If the user tries to make two outgoing calls to different emergency call numbers,
             //  we will try to connect the first outgoing call and reject the second.
+            emergencyCall.setStartFailCause(CallFailureCause.IN_EMERGENCY_CALL);
             return false;
         }
 
@@ -4302,6 +4881,12 @@
             return true;
         }
 
+        // If the live call is stuck in a connecting state, prompt the user to generate a bugreport.
+        if (liveCall.getState() == CallState.CONNECTING) {
+            mAnomalyReporter.reportAnomaly(LIVE_CALL_STUCK_CONNECTING_EMERGENCY_ERROR_UUID,
+                    LIVE_CALL_STUCK_CONNECTING_EMERGENCY_ERROR_MSG);
+        }
+
         // If we have the max number of held managed calls and we're placing an emergency call,
         // we'll disconnect the ongoing call if it cannot be held.
         if (hasMaximumManagedHoldingCalls(emergencyCall) && !canHold(liveCall)) {
@@ -4373,12 +4958,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;
     }
 
@@ -4400,9 +4987,20 @@
             return true;
         }
 
-        // If the live call is stuck in a connecting state, then we should disconnect it in favor
-        // of the new outgoing call.
-        if (liveCall.getState() == CallState.CONNECTING) {
+        // If the live call is stuck in a connecting state for longer than the transitory timeout,
+        // then we should disconnect it in favor of the new outgoing call and prompt the user to
+        // generate a bugreport.
+        // TODO: In the future we should let the CallAnomalyWatchDog do this disconnection of the
+        // live call stuck in the connecting state.  Unfortunately that code will get tripped up by
+        // calls that have a longer than expected new outgoing call broadcast response time.  This
+        // mitigation is intended to catch calls stuck in a CONNECTING state for a long time that
+        // block outgoing calls.  However, if the user dials two calls in quick succession it will
+        // result in both calls getting disconnected, which is not optimal.
+        if (liveCall.getState() == CallState.CONNECTING
+                && ((mClockProxy.elapsedRealtime() - liveCall.getCreationElapsedRealtimeMillis())
+                > mTimeoutsAdapter.getNonVoipCallTransitoryStateTimeoutMillis())) {
+            mAnomalyReporter.reportAnomaly(LIVE_CALL_STUCK_CONNECTING_ERROR_UUID,
+                    LIVE_CALL_STUCK_CONNECTING_ERROR_MSG);
             liveCall.disconnect("Force disconnect CONNECTING call.");
             return true;
         }
@@ -4418,6 +5016,7 @@
                         + " of new outgoing call.");
                 return true;
             }
+            call.setStartFailCause(CallFailureCause.MAX_OUTGOING_CALLS);
             return false;
         }
 
@@ -4437,12 +5036,25 @@
                     liveCallPhoneAccount);
         }
 
-        // First thing, if we are trying to make a call with the same phone account as the live
-        // call, then allow it so that the connection service can make its own decision about
-        // how to handle the new call relative to the current one.
+        // First thing, for managed calls, if we are trying to make a call with the same phone
+        // account as the live call, then allow it so that the connection service can make its own
+        // decision about how to handle the new call relative to the current one.
+        // Note: This behavior is primarily in place because Telephony historically manages the
+        // state of the calls it tracks by itself, holding and unholding as needed.  Self-managed
+        // calls, even though from the same package are normally held/unheld automatically by
+        // Telecom.  Calls within a single ConnectionService get held/unheld automatically during
+        // "swap" operations by CallsManager#holdActiveCallForNewCall.  There is, however, a quirk
+        // in that if an app declares TWO different ConnectionServices, holdActiveCallForNewCall
+        // would not work correctly because focus switches between ConnectionServices, yet we
+        // tended to assume that if the calls are from the same package that the hold/unhold should
+        // be done by the app.  That was a bad assumption as it meant that we could have two active
+        // calls.
+        // TODO(b/280826075): We need to come back and revisit all this logic in a holistic manner.
         if (PhoneAccountHandle.areFromSamePackage(liveCallPhoneAccount,
-                call.getTargetPhoneAccount())) {
-            Log.i(this, "makeRoomForOutgoingCall: phoneAccount matches.");
+                call.getTargetPhoneAccount())
+                && !call.isSelfManaged()
+                && !liveCall.isSelfManaged()) {
+            Log.i(this, "makeRoomForOutgoingCall: managed phoneAccount matches");
             call.getAnalytics().setCallIsAdditional(true);
             liveCall.getAnalytics().setCallIsInterrupted(true);
             return true;
@@ -4466,6 +5078,7 @@
         }
 
         // The live call cannot be held so we're out of luck here.  There's no room.
+        call.setStartFailCause(CallFailureCause.CANNOT_HOLD_CALL);
         return false;
     }
 
@@ -4509,17 +5122,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();
+            }
+        });
     }
 
     /**
@@ -4566,8 +5209,11 @@
         call.setHandle(connection.getHandle(), connection.getHandlePresentation());
         call.setCallerDisplayName(connection.getCallerDisplayName(),
                 connection.getCallerDisplayNamePresentation());
+        // For existing connections, use the phone account user handle to determine the user
+        // association with the call.
+        call.setAssociatedUser(connection.getPhoneAccount().getUserHandle());
         call.addListener(this);
-        call.putExtras(Call.SOURCE_CONNECTION_SERVICE, connection.getExtras());
+        call.putConnectionServiceExtras(connection.getExtras());
 
         Log.i(this, "createCallForExistingConnection: %s", connection);
         Call parentCall = null;
@@ -4586,6 +5232,9 @@
                 call.setParentCall(parentCall);
             }
         }
+        // Existing connections originate from a connection service, so they are completed creation
+        // by the ConnectionService implicitly.
+        call.setIsCreateConnectionComplete(true);
         addCall(call);
         if (parentCall != null) {
             // Now, set the call as a child of the parent since it has been added to Telecom.  This
@@ -4690,23 +5339,42 @@
 
     public boolean isIncomingCallPermitted(Call excludeCall,
                                            PhoneAccountHandle phoneAccountHandle) {
+        return checkIncomingCallPermitted(excludeCall, phoneAccountHandle).isSuccess();
+    }
+
+    private CallFailureCause checkIncomingCallPermitted(
+            Call call, PhoneAccountHandle phoneAccountHandle) {
         if (phoneAccountHandle == null) {
-            return false;
+            return CallFailureCause.INVALID_USE;
         }
+
         PhoneAccount phoneAccount =
                 mPhoneAccountRegistrar.getPhoneAccountUnchecked(phoneAccountHandle);
         if (phoneAccount == null) {
-            return false;
+            return CallFailureCause.INVALID_USE;
         }
-        if (isInEmergencyCall()) return false;
 
-        if (!phoneAccount.isSelfManaged()) {
-            return !hasMaximumManagedRingingCalls(excludeCall) &&
-                    !hasMaximumManagedHoldingCalls(excludeCall);
-        } else {
-            return !hasMaximumSelfManagedRingingCalls(excludeCall, phoneAccountHandle) &&
-                    !hasMaximumSelfManagedCalls(excludeCall, phoneAccountHandle);
+        if (isInEmergencyCall()) {
+            return CallFailureCause.IN_EMERGENCY_CALL;
         }
+
+        if (phoneAccount.isSelfManaged()) {
+            if (hasMaximumSelfManagedRingingCalls(call, phoneAccountHandle)) {
+                return CallFailureCause.MAX_RINGING_CALLS;
+            }
+            if (hasMaximumSelfManagedCalls(call, phoneAccountHandle)) {
+                return CallFailureCause.MAX_SELF_MANAGED_CALLS;
+            }
+        } else {
+            if (hasMaximumManagedRingingCalls(call)) {
+                return CallFailureCause.MAX_RINGING_CALLS;
+            }
+            if (hasMaximumManagedHoldingCalls(call)) {
+                return CallFailureCause.MAX_HOLD_CALLS;
+            }
+        }
+
+        return CallFailureCause.NONE;
     }
 
     public boolean isOutgoingCallPermitted(PhoneAccountHandle phoneAccountHandle) {
@@ -4878,7 +5546,7 @@
      *
      * @param pw The {@code IndentingPrintWriter} to write the state to.
      */
-    public void dump(IndentingPrintWriter pw) {
+    public void dump(IndentingPrintWriter pw, String[] args) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
         if (mCalls != null) {
             pw.println("mCalls: ");
@@ -4934,6 +5602,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();
@@ -4955,6 +5637,13 @@
             impl.dump(pw);
             pw.decreaseIndent();
         }
+
+        if (mConnectionSvrFocusMgr != null) {
+            pw.println("mConnectionSvrFocusMgr:");
+            pw.increaseIndent();
+            mConnectionSvrFocusMgr.dump(pw);
+            pw.decreaseIndent();
+        }
     }
 
     /**
@@ -4963,11 +5652,14 @@
     * @param call The call.
     */
     private void maybeShowErrorDialogOnDisconnect(Call call) {
-        if (call.getState() == CallState.DISCONNECTED && (isPotentialMMICode(call.getHandle())
-                || isPotentialInCallMMICode(call.getHandle())) && !mCalls.contains(call)) {
+        if (call.getState() == CallState.DISCONNECTED && (mMmiUtils.isPotentialMMICode(
+                call.getHandle())
+                || mMmiUtils.isPotentialInCallMMICode(call.getHandle())) && !mCalls.contains(
+                call)) {
             DisconnectCause disconnectCause = call.getDisconnectCause();
-            if (!TextUtils.isEmpty(disconnectCause.getDescription()) && (disconnectCause.getCode()
-                    == DisconnectCause.ERROR)) {
+            if (!TextUtils.isEmpty(disconnectCause.getDescription()) && ((disconnectCause.getCode()
+                    == DisconnectCause.ERROR) || (disconnectCause.getCode()
+                    == DisconnectCause.RESTRICTED))) {
                 Intent errorIntent = new Intent(mContext, ErrorDialogActivity.class);
                 errorIntent.putExtra(ErrorDialogActivity.ERROR_MESSAGE_STRING_EXTRA,
                         disconnectCause.getDescription());
@@ -5039,6 +5731,9 @@
         } else {
             call.setConnectionService(service);
             service.createConnectionFailed(call);
+            if (!mCalls.contains(call)){
+                mListeners.forEach(l -> l.onCreateConnectionFailed(call));
+            }
         }
     }
 
@@ -5061,9 +5756,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
@@ -5155,7 +5861,9 @@
         if (isSelfManaged) {
             call.setIsVoipAudioMode(true);
         }
-        call.setInitiatingUser(getCurrentUserHandle());
+        // Set associated user based on the existing call as it doesn't make sense to handover calls
+        // across user profiles.
+        call.setAssociatedUser(handoverFromCall.getAssociatedUser());
 
         // Ensure we don't try to place an outgoing call with video if video is not
         // supported.
@@ -5227,7 +5935,7 @@
                 // Disconnect all self-managed calls to make priority for emergency call.
                 disconnectSelfManagedCalls("emergency call");
             }
-
+            notifyStartCreateConnection(call);
             call.startCreateConnection(mPhoneAccountRegistrar);
         }
 
@@ -5389,6 +6097,9 @@
         fromCall.setHandoverDestinationCall(call);
         call.setHandoverSourceCall(fromCall);
         call.setHandoverState(HandoverState.HANDOVER_TO_STARTED);
+        // Set associated user based on the existing call as it doesn't make sense to handover calls
+        // across user profiles.
+        call.setAssociatedUser(fromCall.getAssociatedUser());
         fromCall.setHandoverState(HandoverState.HANDOVER_FROM_STARTED);
 
         if (isSpeakerEnabledForVideoCalls() && VideoProfile.isVideo(videoState)) {
@@ -5404,7 +6115,7 @@
         extras.putBoolean(TelecomManager.EXTRA_IS_HANDOVER_CONNECTION, true);
         extras.putParcelable(TelecomManager.EXTRA_HANDOVER_FROM_PHONE_ACCOUNT,
                 fromCall.getTargetPhoneAccount());
-
+        notifyStartCreateConnection(call);
         call.startCreateConnection(mPhoneAccountRegistrar);
     }
 
@@ -5412,8 +6123,10 @@
         return mConnectionSvrFocusMgr;
     }
 
-    private boolean canHold(Call call) {
-        return call.can(Connection.CAPABILITY_HOLD) && call.getState() != CallState.DIALING;
+    @VisibleForTesting
+    public boolean canHold(Call call) {
+        return ((call.isTransactionalCall() && call.can(Connection.CAPABILITY_SUPPORT_HOLD)) ||
+                call.can(Connection.CAPABILITY_HOLD)) && call.getState() != CallState.DIALING;
     }
 
     private boolean supportsHold(Call call) {
@@ -5435,8 +6148,12 @@
         @Override
         public void performAction() {
             synchronized (mLock) {
-                Log.d(this, "perform set call state for %s, state = %s", mCall, mState);
-                setCallState(mCall, mState, mTag);
+                Log.d(this, "performAction: current call state %s", mCall);
+                if (mCall.getState() != CallState.DISCONNECTED
+                        && mCall.getState() != CallState.DISCONNECTING) {
+                    Log.d(this, "performAction: setting to new state = %s", mState);
+                    setCallState(mCall, mState, mTag);
+                }
             }
         }
     }
@@ -5517,6 +6234,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());
@@ -5536,7 +6329,8 @@
      * call, or a number which has been identified by the number as an emergency call.
      * @return {@code true} if there is an ongoing emergency call, {@code false} otherwise.
      */
-    public boolean isInEmergencyCall() {
+    public boolean
+    isInEmergencyCall() {
         return mCalls.stream().filter(c -> (c.isEmergencyCall()
                 || c.isNetworkIdentifiedEmergencyCall()) && !c.isDisconnected()).count() > 0;
     }
@@ -5585,6 +6379,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.getAssociatedUser().equals(userHandle)
+                || call.getPhoneAccountFromHandle()
+                .hasCapabilities(PhoneAccount.CAPABILITY_MULTI_USER);
+    }
+
+    /**
      * Determines if two {@link Call} instances originated from either the same target
      * {@link PhoneAccountHandle} or connection manager {@link PhoneAccountHandle}.
      * @param call1 The first call
@@ -5665,4 +6472,58 @@
     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;
+    }
+
+    /**
+     * Given a call identified by call id, get the instance from the list of calls.
+     * @param callId the call id.
+     * @return the call, or null if not found.
+     */
+    public @Nullable Call getCall(@NonNull String callId) {
+        Optional<Call> foundCall = mCalls.stream().filter(
+                c -> c.getId().equals(callId)).findFirst();
+        if (foundCall.isPresent()) {
+            return foundCall.get();
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Triggers stopping of call streaming for a call by launching a stop streaming transaction.
+     * @param call the call.
+     */
+    public void stopCallStreaming(@NonNull Call call) {
+        if (call.getTransactionServiceWrapper() == null) {
+            return;
+        }
+        call.getTransactionServiceWrapper().stopCallStreaming(call);
+    }
+
+    @VisibleForTesting
+    public Set<Call> getSelfManagedCallsBeingSetup() {
+        return mSelfManagedCallsBeingSetup;
+    }
+
+    @VisibleForTesting
+    public void addCallBeingSetup(Call call) {
+        mSelfManagedCallsBeingSetup.add(call);
+    }
 }
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..3694727 100644
--- a/src/com/android/server/telecom/ConnectionServiceFocusManager.java
+++ b/src/com/android/server/telecom/ConnectionServiceFocusManager.java
@@ -25,13 +25,16 @@
 import android.telecom.Log;
 import android.telecom.Logging.Session;
 import android.text.TextUtils;
+import android.util.LocalLog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
 
 import java.util.ArrayList;
 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;
@@ -40,6 +43,7 @@
 public class ConnectionServiceFocusManager {
     private static final String TAG = "ConnectionSvrFocusMgr";
     private static final int GET_CURRENT_FOCUS_TIMEOUT_MILLIS = 1000;
+    private final LocalLog mLocalLog = new LocalLog(20);
 
     /** Factory interface used to create the {@link ConnectionServiceFocusManager} instance. */
     public interface ConnectionServiceFocusManagerFactory {
@@ -123,6 +127,11 @@
          * @return {@code True} if this call can receive focus, {@code false} otherwise.
          */
         boolean isFocusable();
+
+        /**
+         * @return the ID of the focusable for debug purposes.
+         */
+        String getId();
     }
 
     /** Interface define a call back for focus request event. */
@@ -153,9 +162,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,20 +357,23 @@
     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);
         }
     }
 
     private void updateCurrentFocusCall() {
+        CallFocus previousFocus = mCurrentFocusCall;
         mCurrentFocusCall = null;
 
         if (mCurrentFocus == null) {
+            Log.i(this, "updateCurrentFocusCall: mCurrentFocus is null");
             return;
         }
 
@@ -371,17 +383,20 @@
                         && 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;
+                if (previousFocus != call) {
+                    mLocalLog.log(call.getId());
                 }
+                Log.i(this, "updateCurrentFocusCall %s", mCurrentFocusCall);
+                return;
             }
         }
-
-        Log.d(this, "updateCurrentFocusCall = null");
+        if (previousFocus != null) {
+            mLocalLog.log("<none>");
+        }
+        Log.i(this, "updateCurrentFocusCall = null");
     }
 
     private void onRequestFocusDone(FocusRequest focusRequest) {
@@ -476,6 +491,11 @@
         }
     }
 
+    public void dump(IndentingPrintWriter pw) {
+        pw.println("Call Focus History:");
+        mLocalLog.dump(pw);
+    }
+
     private final class FocusManagerHandler extends Handler {
         FocusManagerHandler(Looper looper) {
             super(looper);
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index 5bb1dbe..c550ede 100755
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -23,15 +23,22 @@
 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.graphics.drawable.Icon;
 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 +49,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 +72,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 +90,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 {
 
@@ -83,10 +101,17 @@
                 ParcelableConnection connection, Session.Info sessionInfo) {
             Log.startSession(sessionInfo, LogUtils.Sessions.CSW_HANDLE_CREATE_CONNECTION_COMPLETE,
                     mPackageAbbreviation);
+            UserHandle callingUserHandle = Binder.getCallingUserHandle();
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
                     logIncoming("handleCreateConnectionComplete %s", callId);
+                    // Check status hints image for cross user access
+                    if (connection.getStatusHints() != null) {
+                        Icon icon = connection.getStatusHints().getIcon();
+                        connection.getStatusHints().setIcon(StatusHints.
+                                validateAccountIconUserBoundary(icon, callingUserHandle));
+                    }
                     ConnectionServiceWrapper.this
                             .handleCreateConnectionComplete(callId, request, connection);
 
@@ -358,9 +383,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 +459,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);
@@ -482,6 +512,14 @@
             Log.startSession(sessionInfo, LogUtils.Sessions.CSW_ADD_CONFERENCE_CALL,
                     mPackageAbbreviation);
 
+            UserHandle callingUserHandle = Binder.getCallingUserHandle();
+            // Check status hints image for cross user access
+            if (parcelableConference.getStatusHints() != null) {
+                Icon icon = parcelableConference.getStatusHints().getIcon();
+                parcelableConference.getStatusHints().setIcon(StatusHints.
+                        validateAccountIconUserBoundary(icon, callingUserHandle));
+            }
+
             if (parcelableConference.getConnectElapsedTimeMillis() != 0
                     && mContext.checkCallingOrSelfPermission(MODIFY_PHONE_STATE)
                             != PackageManager.PERMISSION_GRANTED) {
@@ -724,13 +762,40 @@
         }
 
         @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);
+            UserHandle callingUserHandle = Binder.getCallingUserHandle();
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
                     logIncoming("setStatusHints %s %s", callId, statusHints);
+                    // Check status hints image for cross user access
+                    if (statusHints != null) {
+                        Icon icon = statusHints.getIcon();
+                        statusHints.setIcon(StatusHints.validateAccountIconUserBoundary(
+                                icon, callingUserHandle));
+                    }
                     Call call = mCallIdMapper.getCall(callId);
                     if (call != null) {
                         call.setStatusHints(statusHints);
@@ -754,7 +819,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) {
@@ -877,6 +942,9 @@
                         callingPhoneAccountHandle.getComponentName().getPackageName());
             }
 
+            boolean hasCrossUserAccess = mContext.checkCallingOrSelfPermission(
+                    android.Manifest.permission.INTERACT_ACROSS_USERS)
+                    == PackageManager.PERMISSION_GRANTED;
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
@@ -888,7 +956,7 @@
                     // an emergency call.
                             mPhoneAccountRegistrar.getCallCapablePhoneAccounts(null /*uriScheme*/,
                             false /*includeDisabledAccounts*/, userHandle, 0 /*capabilities*/,
-                            0 /*excludedCapabilities*/);
+                            0 /*excludedCapabilities*/, hasCrossUserAccess);
                     PhoneAccountHandle phoneAccountHandle = null;
                     for (PhoneAccountHandle accountHandle : accountHandles) {
                         if(accountHandle.equals(callingPhoneAccountHandle)) {
@@ -962,6 +1030,14 @@
                                     connection.getCallDirection(),
                                     connection.getCallerNumberVerificationStatus());
                         }
+
+                        // Check status hints image for cross user access
+                        if (connection.getStatusHints() != null) {
+                            Icon icon = connection.getStatusHints().getIcon();
+                            connection.getStatusHints().setIcon(StatusHints.
+                                    validateAccountIconUserBoundary(icon, userHandle));
+                        }
+
                         // Check to see if this Connection has already been added.
                         Call alreadyAddedConnection = mCallsManager
                                 .getAlreadyAddedConnection(connectIdToCheck);
@@ -1196,6 +1272,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 +1363,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 +1424,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 +1634,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 +1951,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..19691c1 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.getAssociatedUser();
             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
@@ -416,19 +429,28 @@
             // Get user preferred PA if it exists.
             PhoneAccount preferredPA = mPhoneAccountRegistrar.getPhoneAccountUnchecked(
                     preferredPAH);
-            // Next, add all SIM phone accounts which can place emergency calls.
-            sortSimPhoneAccountsForEmergency(allAccounts, preferredPA);
-            // and pick the first one that can place emergency calls.
-            for (PhoneAccount phoneAccount : allAccounts) {
-                if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)
-                        && phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
-                    PhoneAccountHandle phoneAccountHandle = phoneAccount.getAccountHandle();
-                    Log.i(this, "Will try PSTN account %s for emergency", phoneAccountHandle);
-                    mAttemptRecords.add(new CallAttemptRecord(phoneAccountHandle,
-                            phoneAccountHandle));
-                    // Add only one emergency SIM PhoneAccount to the attempt list, telephony will
-                    // perform retries if the call fails.
-                    break;
+            if (mCall.isIncoming() && preferredPA != null) {
+                // The phone account for the incoming call should be used.
+                mAttemptRecords.add(new CallAttemptRecord(preferredPA.getAccountHandle(),
+                        preferredPA.getAccountHandle()));
+            } else {
+                // Next, add all SIM phone accounts which can place emergency calls.
+                sortSimPhoneAccountsForEmergency(allAccounts, preferredPA);
+                Log.i(this, "The preferred PA is: %s", preferredPA);
+                // and pick the first one that can place emergency calls.
+                for (PhoneAccount phoneAccount : allAccounts) {
+                    if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)
+                            && phoneAccount.hasCapabilities(
+                            PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
+                        PhoneAccountHandle phoneAccountHandle = phoneAccount.getAccountHandle();
+                        Log.i(this, "Will try PSTN account %s for emergency",
+                                phoneAccountHandle);
+                        mAttemptRecords.add(new CallAttemptRecord(phoneAccountHandle,
+                                phoneAccountHandle));
+                        // Add only one emergency SIM PhoneAccount to the attempt list, telephony
+                        // will perform retries if the call fails.
+                        break;
+                    }
                 }
             }
 
diff --git a/src/com/android/server/telecom/DefaultDialerCache.java b/src/com/android/server/telecom/DefaultDialerCache.java
index a4a0242..dc79715 100644
--- a/src/com/android/server/telecom/DefaultDialerCache.java
+++ b/src/com/android/server/telecom/DefaultDialerCache.java
@@ -160,6 +160,7 @@
         packageIntentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
         packageIntentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
         packageIntentFilter.addDataScheme("package");
+        packageIntentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
         context.registerReceiverAsUser(mReceiver, UserHandle.ALL, packageIntentFilter, null, null);
 
         IntentFilter bootIntentFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
@@ -265,7 +266,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/DockManager.java b/src/com/android/server/telecom/DockManager.java
index dda5711..114672e 100644
--- a/src/com/android/server/telecom/DockManager.java
+++ b/src/com/android/server/telecom/DockManager.java
@@ -76,6 +76,7 @@
 
         // Register for misc other intent broadcasts.
         IntentFilter intentFilter = new IntentFilter(Intent.ACTION_DOCK_EVENT);
+        intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
         context.registerReceiver(mReceiver, intentFilter);
     }
 
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..5ab0e99 100644
--- a/src/com/android/server/telecom/EmergencyCallHelper.java
+++ b/src/com/android/server/telecom/EmergencyCallHelper.java
@@ -21,6 +21,8 @@
 import android.content.pm.PackageManager;
 import android.os.UserHandle;
 import android.telecom.Log;
+import android.telecom.PhoneAccountHandle;
+
 import com.android.internal.annotations.VisibleForTesting;
 
 /**
@@ -34,9 +36,20 @@
     private final DefaultDialerCache mDefaultDialerCache;
     private final Timeouts.Adapter mTimeoutsAdapter;
     private UserHandle mLocationPermissionGrantedToUser;
+    private PhoneAccountHandle mLastOutgoingEmergencyCallPAH;
+
+    //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;
+    private long mLastOutgoingEmergencyCallTimestampMillis;
 
     @VisibleForTesting
     public EmergencyCallHelper(
@@ -48,16 +61,18 @@
         mTimeoutsAdapter = timeoutsAdapter;
     }
 
-    void maybeGrantTemporaryLocationPermission(Call call, UserHandle userHandle) {
+    @VisibleForTesting
+    public void maybeGrantTemporaryLocationPermission(Call call, UserHandle userHandle) {
         if (shouldGrantTemporaryLocationPermission(call)) {
             grantLocationPermission(userHandle);
         }
         if (call != null && call.isEmergencyCall()) {
-            recordEmergencyCallTime();
+            recordEmergencyCall(call);
         }
     }
 
-    void maybeRevokeTemporaryLocationPermission() {
+    @VisibleForTesting
+    public void maybeRevokeTemporaryLocationPermission() {
         if (wasGrantedTemporaryLocationPermission()) {
             revokeLocationPermission();
         }
@@ -67,15 +82,44 @@
         return mLastEmergencyCallTimestampMillis;
     }
 
-    private void recordEmergencyCallTime() {
-        mLastEmergencyCallTimestampMillis = System.currentTimeMillis();
+    @VisibleForTesting
+    public void setLastOutgoingEmergencyCallTimestampMillis(long timestampMillis) {
+        mLastOutgoingEmergencyCallTimestampMillis = timestampMillis;
     }
 
-    private boolean isInEmergencyCallbackWindow() {
-        return System.currentTimeMillis() - getLastEmergencyCallTimeMillis()
+    @VisibleForTesting
+    public void setLastOutgoingEmergencyCallPAH(PhoneAccountHandle accountHandle) {
+        mLastOutgoingEmergencyCallPAH = accountHandle;
+    }
+
+    @VisibleForTesting
+    public boolean isLastOutgoingEmergencyCallPAH(PhoneAccountHandle currentCallHandle) {
+        boolean ecbmActive = mLastOutgoingEmergencyCallPAH != null
+                && isInEmergencyCallbackWindow(mLastOutgoingEmergencyCallTimestampMillis)
+                && currentCallHandle != null
+                && currentCallHandle.equals(mLastOutgoingEmergencyCallPAH);
+        if (ecbmActive) {
+            Log.i(this, "ECBM is enabled for %s. The last recorded call timestamp was at %s",
+                    currentCallHandle, mLastOutgoingEmergencyCallTimestampMillis);
+        }
+
+        return ecbmActive;
+    }
+
+    boolean isInEmergencyCallbackWindow(long lastEmergencyCallTimestampMillis) {
+        return System.currentTimeMillis() - lastEmergencyCallTimestampMillis
                 < mTimeoutsAdapter.getEmergencyCallbackWindowMillis(mContext.getContentResolver());
     }
 
+    private void recordEmergencyCall(Call call) {
+        mLastEmergencyCallTimestampMillis = System.currentTimeMillis();
+        if (!call.isIncoming()) {
+            // ECBM is applicable to MO emergency calls
+            mLastOutgoingEmergencyCallTimestampMillis = mLastEmergencyCallTimestampMillis;
+            mLastOutgoingEmergencyCallPAH = call.getTargetPhoneAccount();
+        }
+    }
+
     private boolean shouldGrantTemporaryLocationPermission(Call call) {
         if (!mContext.getResources().getBoolean(R.bool.grant_location_permission_enabled)) {
             Log.i(this, "ShouldGrantTemporaryLocationPermission, disabled by config");
@@ -85,7 +129,8 @@
             Log.i(this, "ShouldGrantTemporaryLocationPermission, no call");
             return false;
         }
-        if (!call.isEmergencyCall() && !isInEmergencyCallbackWindow()) {
+        if (!call.isEmergencyCall() && !isInEmergencyCallbackWindow(
+                getLastEmergencyCallTimeMillis())) {
             Log.i(this, "ShouldGrantTemporaryLocationPermission, not emergency");
             return false;
         }
@@ -95,51 +140,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 +216,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 +234,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 c469308..d1df751 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;
@@ -51,7 +47,9 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.permission.PermissionManager;
 import android.telecom.CallAudioState;
+import android.telecom.CallEndpoint;
 import android.telecom.ConnectionService;
 import android.telecom.InCallService;
 import android.telecom.Log;
@@ -78,6 +76,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 +90,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
@@ -133,16 +147,20 @@
         private long mBindingStartTime;
         private long mDisconnectTime;
 
+        private boolean mHasCrossUserOrProfilePerm;
+
         public InCallServiceInfo(ComponentName componentName,
                 boolean isExternalCallsSupported,
                 boolean isSelfManageCallsSupported,
-                int type) {
+                int type, boolean hasCrossUserOrProfilePerm) {
             mComponentName = componentName;
             mIsExternalCallsSupported = isExternalCallsSupported;
             mIsSelfManagedCallsSupported = isSelfManageCallsSupported;
             mType = type;
+            mHasCrossUserOrProfilePerm = hasCrossUserOrProfilePerm;
         }
 
+        public boolean hasCrossUserOrProfilePermission() { return mHasCrossUserOrProfilePerm; }
         public ComponentName getComponentName() {
             return mComponentName;
         }
@@ -279,12 +297,25 @@
         private boolean mIsNullBinding = false;
         private NotificationManager mNotificationManager;
 
+        //this is really used for cases where the userhandle for a call
+        //does not match what we want to use for bindAsUser
+        private final UserHandle mUserHandleToUseForBinding;
+
         public InCallServiceBindingConnection(InCallServiceInfo info) {
             mInCallServiceInfo = info;
+            mUserHandleToUseForBinding = null;
+        }
+
+        public InCallServiceBindingConnection(InCallServiceInfo info,
+                UserHandle userHandleToUseForBinding) {
+            mInCallServiceInfo = info;
+            mUserHandleToUseForBinding = userHandleToUseForBinding;
         }
 
         @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 +325,7 @@
 
                     // Notify this new added call
                     sendCallToService(call, mInCallServiceInfo,
-                            mInCallServices.get(mInCallServiceInfo));
+                            mInCallServices.get(userFromCall).get(mInCallServiceInfo));
                 }
                 return CONNECTION_SUCCEEDED;
             }
@@ -320,11 +351,35 @@
             Log.i(this, "Attempting to bind to InCall %s, with %s", mInCallServiceInfo, intent);
             mIsConnected = true;
             mInCallServiceInfo.setBindingStartTime(mClockProxy.elapsedRealtime());
+            boolean isManagedProfile = UserUtil.isManagedProfile(mContext, userFromCall);
+            // Note that UserHandle.CURRENT fails to capture the work profile, so we need to handle
+            // it separately to ensure that the ICS is bound to the appropriate user. If ECBM is
+            // active, we know that a work sim was previously used to place a MO emergency call. We
+            // need to ensure that we bind to the CURRENT_USER in this case, as the work user would
+            // not be running (handled in getUserFromCall).
+            UserHandle userToBind = isManagedProfile ? userFromCall : UserHandle.CURRENT;
+            if ((mInCallServiceInfo.mType == IN_CALL_SERVICE_TYPE_NON_UI
+                    || mInCallServiceInfo.mType == IN_CALL_SERVICE_TYPE_CAR_MODE_UI) && (
+                    mUserHandleToUseForBinding != null)) {
+                //guarding change for non-UI/carmode-UI services which may not be present for
+                // work profile.
+                //In this case, we use the parent user handle. (This also seems to be more
+                // accurate that USER_CURRENT since we queried/discovered the packages using the
+                // parent handle)
+                if (mInCallServiceInfo.hasCrossUserOrProfilePermission()) {
+                    userToBind = mUserHandleToUseForBinding;
+                } else {
+                    Log.i(this,
+                            "service does not have INTERACT_ACROSS_PROFILES or "
+                                    + "INTERACT_ACROSS_USERS permission");
+                }
+            }
+            Log.i(this, "using user id: %s for binding. User from Call is: %s", userToBind,
+                    userFromCall);
             if (!mContext.bindServiceAsUser(intent, mServiceConnection,
                     Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
                         | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS
-                        | Context.BIND_SCHEDULE_LIKE_TOP_APP,
-                    UserHandle.CURRENT)) {
+                        | Context.BIND_SCHEDULE_LIKE_TOP_APP, userToBind)) {
                 Log.w(this, "Failed to connect.");
                 mIsConnected = false;
             }
@@ -345,6 +400,7 @@
         @Override
         public void disconnect() {
             if (mIsConnected) {
+                UserHandle userFromCall = getUserFromCall(mCall);
                 mInCallServiceInfo.setDisconnectTime(mClockProxy.elapsedRealtime());
                 Log.i(InCallController.this, "ICSBC#disconnect: unbinding after %s ms;"
                                 + "%s. isCrashed: %s", mInCallServiceInfo.mDisconnectTime
@@ -357,7 +413,7 @@
                     // Non-UI InCallServices are allowed to return null from onBind if they don't
                     // want to handle calls at the moment, so don't report them to the user as
                     // crashed.
-                    sendCrashedInCallServiceNotification(packageName);
+                    sendCrashedInCallServiceNotification(packageName, userFromCall);
                 }
                 if (mCall != null) {
                     mCall.getAnalytics().addInCallService(
@@ -368,7 +424,7 @@
                     updateCallTracking(mCall, mInCallServiceInfo, false /* isAdd */);
                 }
 
-                InCallController.this.onDisconnected(mInCallServiceInfo);
+                InCallController.this.onDisconnected(mInCallServiceInfo, userFromCall);
             } else {
                 Log.i(InCallController.this, "ICSBC#disconnect: already disconnected; %s",
                         mInCallServiceInfo);
@@ -394,7 +450,8 @@
 
         protected void onConnected(IBinder service) {
             boolean shouldRemainConnected =
-                    InCallController.this.onConnected(mInCallServiceInfo, service);
+                    InCallController.this.onConnected(mInCallServiceInfo, service,
+                            getUserFromCall(mCall));
             if (!shouldRemainConnected) {
                 // Sometimes we can opt to disconnect for certain reasons, like if the
                 // InCallService rejected our initialization step, or the calls went away
@@ -405,11 +462,25 @@
         }
 
         protected void onDisconnected() {
-            InCallController.this.onDisconnected(mInCallServiceInfo);
+            boolean shouldReconnect = mIsConnected;
+            InCallController.this.onDisconnected(mInCallServiceInfo, getUserFromCall(mCall));
             disconnect();  // Unbind explicitly if we get disconnected.
             if (mListener != null) {
                 mListener.onDisconnect(InCallServiceBindingConnection.this, mCall);
             }
+            // Check if we are expected to reconnect
+            if (shouldReconnect && shouldHandleReconnect()) {
+                connect(mCall);  // reconnect
+            }
+        }
+
+        private boolean shouldHandleReconnect() {
+            int serviceType = mInCallServiceInfo.getType();
+            boolean nonUI = (serviceType == IN_CALL_SERVICE_TYPE_NON_UI)
+                    || (serviceType == IN_CALL_SERVICE_TYPE_COMPANION);
+            boolean carModeUI = (serviceType == IN_CALL_SERVICE_TYPE_CAR_MODE_UI);
+
+            return carModeUI || (nonUI && !mIsNullBinding);
         }
     }
 
@@ -462,9 +533,9 @@
                 // Could not connect to child, stop proxying.
                 mIsProxying = false;
             }
-
+            UserHandle userFromCall = getUserFromCall(call);
             mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(call,
-                    mCallsManager.getCurrentUserHandle());
+                    userFromCall);
 
             if (call != null && call.isIncoming()
                     && mEmergencyCallHelper.getLastEmergencyCallTimeMillis() > 0) {
@@ -472,7 +543,7 @@
                 Bundle extras = new Bundle();
                 extras.putLong(android.telecom.Call.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS,
                         mEmergencyCallHelper.getLastEmergencyCallTimeMillis());
-                call.putExtras(Call.SOURCE_CONNECTION_SERVICE, extras);
+                call.putConnectionServiceExtras(extras);
             }
 
             // If we are here, we didn't or could not connect to child. So lets connect ourselves.
@@ -612,13 +683,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)
@@ -630,7 +701,7 @@
                 }
 
                 mCarModeConnection = mCurrentConnection =
-                        new InCallServiceBindingConnection(carModeConnectionInfo);
+                        new InCallServiceBindingConnection(carModeConnectionInfo, userHandle);
                 mIsCarMode = true;
 
                 int result = mCurrentConnection.connect(null);
@@ -770,6 +841,9 @@
             }
             Call callToConnectWith = mCallIdMapper.getCalls().iterator().next();
             for (InCallServiceBindingConnection newConnection : newConnections) {
+                // Ensure we track the new sub-connection so that when we later disconnect we will
+                // be able to disconnect it.
+                mSubConnections.add(newConnection);
                 newConnection.connect(callToConnectWith);
             }
         }
@@ -787,7 +861,7 @@
 
         @Override
         public void onConnectionPropertiesChanged(Call call, boolean didRttChange) {
-            updateCall(call, false /* includeVideoProvider */, didRttChange);
+            updateCall(call, false /* includeVideoProvider */, didRttChange, null);
         }
 
         @Override
@@ -797,7 +871,7 @@
 
         @Override
         public void onVideoCallProviderChanged(Call call) {
-            updateCall(call, true /* videoProviderChanged */, false);
+            updateCall(call, true /* videoProviderChanged */, false, null);
         }
 
         @Override
@@ -805,25 +879,35 @@
             updateCall(call);
         }
 
+        @Override
+        public void onCallerInfoChanged(Call call) {
+            updateCall(call);
+        }
+
         /**
          * Listens for changes to extras reported by a Telecom {@link Call}.
          *
          * Extras changes can originate from a {@link ConnectionService} or an {@link InCallService}
-         * so we will only trigger an update of the call information if the source of the extras
-         * change was a {@link ConnectionService}.
+         * so we will only trigger an update of the call information if the source of the
+         * extras change was a {@link ConnectionService}.
          *
-         * @param call The call.
-         * @param source The source of the extras change ({@link Call#SOURCE_CONNECTION_SERVICE} or
+         * @param call   The call.
+         * @param source The source of the extras change
+         *               ({@link Call#SOURCE_CONNECTION_SERVICE} or
          *               {@link Call#SOURCE_INCALL_SERVICE}).
          * @param extras The extras.
          */
         @Override
-        public void onExtrasChanged(Call call, int source, Bundle extras) {
-            // Do not inform InCallServices of changes which originated there.
-            if (source == Call.SOURCE_INCALL_SERVICE) {
-                return;
+        public void onExtrasChanged(Call call, int source, Bundle extras,
+                String requestingPackageName) {
+            if (source == Call.SOURCE_CONNECTION_SERVICE) {
+                updateCall(call);
+            } else if (source == Call.SOURCE_INCALL_SERVICE && requestingPackageName != null) {
+                // If the change originated from another InCallService, we'll propagate the change
+                // to all other InCallServices running, EXCEPT the one who made the original change.
+                updateCall(call, false /* videoProviderChanged */, false /* rttInfoChanged */,
+                        requestingPackageName);
             }
-            updateCall(call);
         }
 
         /**
@@ -894,7 +978,7 @@
         @Override
         public void onRttInitiationFailure(Call call, int reason) {
             notifyRttInitiationFailure(call, reason);
-            updateCall(call, false, true);
+            updateCall(call, false, true, null);
         }
 
         @Override
@@ -909,31 +993,137 @@
         }
     };
 
+    private UserHandle findChildManagedProfileUser(UserHandle parent, UserManager um) {
+        UserHandle childManagedProfileUser = null;
+
+        //find child managed profile user (if any)
+        List<UserHandle> allUsers = um.getAllProfiles();
+        for (UserHandle u : allUsers) {
+            if ((um.getProfileParent(u) != null) && (um.getProfileParent(u).equals(parent))
+                    && um.isManagedProfile(u.getIdentifier())) {
+                //found managed profile child
+                Log.i(this,
+                        "Child managed profile user found: " + u.getIdentifier());
+                childManagedProfileUser = u;
+                break;
+            }
+        }
+        return childManagedProfileUser;
+    }
     private BroadcastReceiver mPackageChangedReceiver = new BroadcastReceiver() {
+        private List<InCallController.InCallServiceInfo> getNonUiInCallServiceInfoList(
+                Intent intent, UserHandle userHandle) {
+            String changedPackage = intent.getData().getSchemeSpecificPart();
+            List<InCallController.InCallServiceInfo> inCallServiceInfoList =
+                    Arrays.stream(intent.getStringArrayExtra(
+                                    Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST))
+                            .map((className) ->
+                                    ComponentName.createRelative(changedPackage,
+                                            className))
+                            .filter(mKnownNonUiInCallServices::contains)
+                            .flatMap(componentName -> getInCallServiceComponents(
+                                    userHandle, componentName,
+                                    IN_CALL_SERVICE_TYPE_NON_UI).stream())
+                            .collect(Collectors.toList());
+            return ((inCallServiceInfoList != null) ? inCallServiceInfoList : new ArrayList<>());
+        }
+
+        //Here we query components using the userHandle. We then also query components using the
+        //parent userHandle (if any) while removing duplicates. For non-dup components found using
+        //parent userHandle, we use the overloaded InCallServiceBindingConnection constructor.
+        @SuppressWarnings("ReturnValueIgnored")
+        private List<InCallServiceBindingConnection> getNonUiInCallServiceBindingConnectionList(
+                Intent intent, @NonNull UserHandle userHandle, UserHandle parentUserHandle) {
+            List<InCallServiceBindingConnection> result = new ArrayList<>();
+            List<InCallController.InCallServiceInfo> serviceInfoListForParent = new ArrayList<>();
+
+            //query and add components for the child
+            List<InCallController.InCallServiceInfo> serviceInfoListForUser =
+                    getNonUiInCallServiceInfoList(intent, userHandle);
+
+            //if user has a parent, get components for parents
+            if (parentUserHandle != null) {
+                serviceInfoListForParent = getNonUiInCallServiceInfoList(intent, parentUserHandle);
+            }
+
+            serviceInfoListForUser
+                    .stream()
+                    .map(InCallServiceBindingConnection::new)
+                    .collect(Collectors.toCollection(() -> result));
+
+            serviceInfoListForParent
+                    .stream()
+                    .filter((e) -> !(serviceInfoListForUser.contains(e)))
+                    .map((serviceinfo) -> new InCallServiceBindingConnection(serviceinfo,
+                            parentUserHandle))
+                    .collect(Collectors.toCollection(() -> result));
+
+            return result;
+        }
+
         @Override
         public void onReceive(Context context, Intent intent) {
             Log.startSession("ICC.pCR");
+            UserManager um = mContext.getSystemService(UserManager.class);
             try {
                 if (Intent.ACTION_PACKAGE_CHANGED.equals(intent.getAction())) {
                     synchronized (mLock) {
-                        String changedPackage = intent.getData().getSchemeSpecificPart();
-                        List<InCallServiceBindingConnection> componentsToBind =
-                                Arrays.stream(intent.getStringArrayExtra(
-                                        Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST))
-                                        .map((className) ->
-                                                ComponentName.createRelative(changedPackage,
-                                                        className))
-                                        .filter(mKnownNonUiInCallServices::contains)
-                                        .flatMap(componentName -> getInCallServiceComponents(
-                                                componentName,
-                                                IN_CALL_SERVICE_TYPE_NON_UI).stream())
-                                        .map(InCallServiceBindingConnection::new)
-                                        .collect(Collectors.toList());
+                        int uid = intent.getIntExtra(Intent.EXTRA_UID, 0);
+                        UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
+                        boolean isManagedProfile = um.isManagedProfile(userHandle.getIdentifier());
 
-                        if (mNonUIInCallServiceConnections != null) {
-                            mNonUIInCallServiceConnections.addConnections(componentsToBind);
+                        /*
+                        There are two possibilities here:
+                         1) We get a work-profile/managed userHandle. In this case we need to check
+                         if there are any ongoing calls for that user. If yes, then process further
+                         by querying component using this user handle (also bindAsUser using this
+                          handle). Else safely ignore it.
+                                OR
+                         2) We get the primary/non-managed userHandle. In this case, we have two
+                          sub-cases to handle:
+                                   a) If there are ongoing calls for this user, query components
+                                   using this user and addConnections
+                                   b) If there are ongoing calls for the child of this user, we
+                                   also addConnections to that child (but invoke bindAsUser later
+                                    with the parent handle).
+
+                         */
+
+                        UserHandle childManagedProfileUser = findChildManagedProfileUser(
+                                userHandle, um);
+                        boolean isUserKeyPresent = mNonUIInCallServiceConnections.containsKey(
+                                userHandle);
+                        boolean isChildUserKeyPresent = (childManagedProfileUser == null) ? false
+                                : mNonUIInCallServiceConnections.containsKey(
+                                        childManagedProfileUser);
+                        List<InCallServiceBindingConnection> componentsToBindForUser = null;
+                        List<InCallServiceBindingConnection> componentsToBindForChild = null;
+
+                        if(isUserKeyPresent) {
+                            componentsToBindForUser =
+                                    getNonUiInCallServiceBindingConnectionList(intent,
+                                            userHandle, null);
+                        }
+                        if (isChildUserKeyPresent) {
+                            componentsToBindForChild =
+                                    getNonUiInCallServiceBindingConnectionList(intent,
+                                            childManagedProfileUser, userHandle);
                         }
 
+                        Log.i(this,
+                                "isUserKeyPresent:%b isChildKeyPresent:%b isManagedProfile:%b "
+                                        + "user:%d",
+                                isUserKeyPresent, isChildUserKeyPresent, isManagedProfile,
+                                userHandle.getIdentifier());
+
+                        if (isUserKeyPresent && componentsToBindForUser != null) {
+                            mNonUIInCallServiceConnections.get(userHandle).
+                                    addConnections(componentsToBindForUser);
+                        }
+                        if (isChildUserKeyPresent && componentsToBindForChild != null) {
+                            mNonUIInCallServiceConnections.get(childManagedProfileUser).
+                                    addConnections(componentsToBindForChild);
+                        }
                         // If the current car mode app become enabled from disabled, update
                         // the connection to binding
                         updateCarModeForConnections();
@@ -988,7 +1178,8 @@
             CallState.DISCONNECTING };
 
     /** The in-call app implementations, see {@link IInCallService}. */
-    private final Map<InCallServiceInfo, IInCallService> mInCallServices = new ArrayMap<>();
+    private final Map<UserHandle, Map<InCallServiceInfo, IInCallService>>
+            mInCallServices = new ArrayMap<>();
 
     private final CallIdMapper mCallIdMapper = new CallIdMapper(Call::getId);
 
@@ -1002,8 +1193,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();
 
@@ -1062,7 +1255,9 @@
         mSystemStateHelper.addListener(mSystemStateListener);
         mClockProxy = clockProxy;
         restrictPhoneCallOps();
-        mContext.registerReceiver(mUserAddedReceiver, new IntentFilter(Intent.ACTION_USER_ADDED));
+        IntentFilter userAddedFilter = new IntentFilter(Intent.ACTION_USER_ADDED);
+        userAddedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+        mContext.registerReceiver(mUserAddedReceiver, userAddedFilter);
     }
 
     private void restrictPhoneCallOps() {
@@ -1136,59 +1331,71 @@
 
     @Override
     public void onCallAdded(Call call) {
-        if (!isBoundAndConnectedToServices()) {
+        UserHandle userFromCall = getUserFromCall(call);
+
+        Log.i(this, "onCallAdded: %s", call);
+        // Track the call if we don't already know about it.
+        addCall(call);
+
+        if (!isBoundAndConnectedToServices(userFromCall)) {
             Log.i(this, "onCallAdded: %s; not bound or connected.", call);
             // We are not bound, or we're not connected.
             bindToServices(call);
         } else {
+            InCallServiceConnection inCallServiceConnection =
+                    mInCallServiceConnections.get(userFromCall);
+
             // We are bound, and we are connected.
-            adjustServiceBindingsForEmergency();
+            adjustServiceBindingsForEmergency(userFromCall);
 
             // This is in case an emergency call is added while there is an existing call.
             mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(call,
-                    mCallsManager.getCurrentUserHandle());
+                    userFromCall);
 
-            Log.i(this, "onCallAdded: %s", call);
-            // Track the call if we don't already know about it.
-            addCall(call);
-
-            Log.i(this, "mInCallServiceConnection isConnected=%b",
-                    mInCallServiceConnection.isConnected());
+            if (inCallServiceConnection != null) {
+                Log.i(this, "mInCallServiceConnection isConnected=%b",
+                        inCallServiceConnection.isConnected());
+            }
 
             List<ComponentName> componentsUpdated = new ArrayList<>();
-            for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
-                InCallServiceInfo info = entry.getKey();
+            if (mInCallServices.containsKey(userFromCall)) {
+                for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.
+                        get(userFromCall).entrySet()) {
+                    InCallServiceInfo info = entry.getKey();
 
-                if (call.isExternalCall() && !info.isExternalCallsSupported()) {
-                    continue;
+                    if (call.isExternalCall() && !info.isExternalCallsSupported()) {
+                        continue;
+                    }
+
+                    if (call.isSelfManaged() && (!call.visibleToInCallService()
+                            || !info.isSelfManagedCallsSupported())) {
+                        continue;
+                    }
+
+                    // Only send the RTT call if it's a UI in-call service
+                    boolean includeRttCall = false;
+                    if (inCallServiceConnection != null) {
+                        includeRttCall = info.equals(inCallServiceConnection.getInfo());
+                    }
+
+                    componentsUpdated.add(info.getComponentName());
+                    IInCallService inCallService = entry.getValue();
+
+                    ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call,
+                            true /* includeVideoProvider */,
+                            mCallsManager.getPhoneAccountRegistrar(),
+                            info.isExternalCallsSupported(), includeRttCall,
+                            info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI ||
+                                    info.getType() == IN_CALL_SERVICE_TYPE_NON_UI);
+                    try {
+                        inCallService.addCall(
+                                sanitizeParcelableCallForService(info, parcelableCall));
+                        updateCallTracking(call, info, true /* isAdd */);
+                    } catch (RemoteException ignored) {
+                    }
                 }
-
-                if (call.isSelfManaged() && (!call.visibleToInCallService()
-                        || !info.isSelfManagedCallsSupported())) {
-                    continue;
-                }
-
-                // Only send the RTT call if it's a UI in-call service
-                boolean includeRttCall = false;
-                if (mInCallServiceConnection != null) {
-                    includeRttCall = info.equals(mInCallServiceConnection.getInfo());
-                }
-
-                componentsUpdated.add(info.getComponentName());
-                IInCallService inCallService = entry.getValue();
-
-                ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call,
-                        true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar(),
-                        info.isExternalCallsSupported(), includeRttCall,
-                        info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI ||
-                                info.getType() == IN_CALL_SERVICE_TYPE_NON_UI);
-                try {
-                    inCallService.addCall(sanitizeParcelableCallForService(info, parcelableCall));
-                    updateCallTracking(call, info, true /* isAdd */);
-                } catch (RemoteException ignored) {
-                }
+                Log.i(this, "Call added to components: %s", componentsUpdated);
             }
-            Log.i(this, "Call added to components: %s", componentsUpdated);
         }
     }
 
@@ -1204,7 +1411,7 @@
                 public void loggedRun() {
                     // Check again to make sure there are no active calls.
                     if (mCallsManager.getCalls().isEmpty()) {
-                        unbindFromServices();
+                        unbindFromServices(getUserFromCall(call));
 
                         mEmergencyCallHelper.maybeRevokeTemporaryLocationPermission();
                     }
@@ -1227,10 +1434,12 @@
         Log.i(this, "onExternalCallChanged: %s -> %b", call, isExternalCall);
 
         List<ComponentName> componentsUpdated = new ArrayList<>();
-        if (!isExternalCall) {
+        UserHandle userFromCall = getUserFromCall(call);
+        if (!isExternalCall && mInCallServices.containsKey(userFromCall)) {
             // The call was external but it is no longer external.  We must now add it to any
             // InCallServices which do not support external calls.
-            for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
+            for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.
+                    get(userFromCall).entrySet()) {
                 InCallServiceInfo info = entry.getKey();
 
                 if (info.isExternalCallsSupported()) {
@@ -1248,7 +1457,8 @@
                 IInCallService inCallService = entry.getValue();
 
                 // Only send the RTT call if it's a UI in-call service
-                boolean includeRttCall = info.equals(mInCallServiceConnection.getInfo());
+                boolean includeRttCall = info.equals(mInCallServiceConnections.
+                        get(userFromCall).getInfo());
 
                 ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call,
                         true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar(),
@@ -1267,35 +1477,38 @@
             // InCallServices which do not support external calls.
             // Remove the call by sending a call update indicating the call was disconnected.
             Log.i(this, "Removing external call %s", call);
-            for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
-                InCallServiceInfo info = entry.getKey();
-                if (info.isExternalCallsSupported()) {
-                    // For InCallServices which support external calls, we do not need to remove
-                    // the call.
-                    continue;
+            if (mInCallServices.containsKey(userFromCall)) {
+                for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.
+                        get(userFromCall).entrySet()) {
+                    InCallServiceInfo info = entry.getKey();
+                    if (info.isExternalCallsSupported()) {
+                        // For InCallServices which support external calls, we do not need to remove
+                        // the call.
+                        continue;
+                    }
+
+                    componentsUpdated.add(info.getComponentName());
+                    IInCallService inCallService = entry.getValue();
+
+                    ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(
+                            call,
+                            false /* includeVideoProvider */,
+                            mCallsManager.getPhoneAccountRegistrar(),
+                            false /* supportsExternalCalls */,
+                            android.telecom.Call.STATE_DISCONNECTED /* overrideState */,
+                            false /* includeRttCall */,
+                            info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI
+                                    || info.getType() == IN_CALL_SERVICE_TYPE_NON_UI
+                    );
+
+                    try {
+                        inCallService.updateCall(
+                                sanitizeParcelableCallForService(info, parcelableCall));
+                    } catch (RemoteException ignored) {
+                    }
                 }
-
-                componentsUpdated.add(info.getComponentName());
-                IInCallService inCallService = entry.getValue();
-
-                ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(
-                        call,
-                        false /* includeVideoProvider */,
-                        mCallsManager.getPhoneAccountRegistrar(),
-                        false /* supportsExternalCalls */,
-                        android.telecom.Call.STATE_DISCONNECTED /* overrideState */,
-                        false /* includeRttCall */,
-                        info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI
-                                || info.getType() == IN_CALL_SERVICE_TYPE_NON_UI
-                );
-
-                try {
-                    inCallService.updateCall(
-                            sanitizeParcelableCallForService(info, parcelableCall));
-                } catch (RemoteException ignored) {
-                }
+                Log.i(this, "External call removed from components: %s", componentsUpdated);
             }
-            Log.i(this, "External call removed from components: %s", componentsUpdated);
         }
         maybeTrackMicrophoneUse(isMuted());
     }
@@ -1321,12 +1534,63 @@
             Log.i(this, "Calling onAudioStateChanged, audioState: %s -> %s", oldCallAudioState,
                     newCallAudioState);
             maybeTrackMicrophoneUse(newCallAudioState.isMuted());
-            for (IInCallService inCallService : mInCallServices.values()) {
-                try {
-                    inCallService.onCallAudioStateChanged(newCallAudioState);
-                } catch (RemoteException ignored) {
+            mInCallServices.values().forEach(inCallServices -> {
+                for (IInCallService inCallService : inCallServices.values()) {
+                    try {
+                        inCallService.onCallAudioStateChanged(newCallAudioState);
+                    } catch (RemoteException ignored) {
+                    }
                 }
-            }
+            });
+        }
+    }
+
+    @Override
+    public void onCallEndpointChanged(CallEndpoint callEndpoint) {
+        if (!mInCallServices.isEmpty()) {
+            Log.i(this, "Calling onCallEndpointChanged");
+            mInCallServices.values().forEach(inCallServices -> {
+                for (IInCallService inCallService : inCallServices.values()) {
+                    try {
+                        inCallService.onCallEndpointChanged(callEndpoint);
+                    } catch (RemoteException ignored) {
+                        Log.d(this, "Remote exception calling onCallEndpointChanged");
+                    }
+                }
+            });
+        }
+    }
+
+    @Override
+    public void onAvailableCallEndpointsChanged(Set<CallEndpoint> availableCallEndpoints) {
+        if (!mInCallServices.isEmpty()) {
+            Log.i(this, "Calling onAvailableCallEndpointsChanged");
+            List<CallEndpoint> availableEndpoints = new ArrayList<>(availableCallEndpoints);
+            mInCallServices.values().forEach(inCallServices -> {
+                for (IInCallService inCallService : inCallServices.values()) {
+                    try {
+                        inCallService.onAvailableCallEndpointsChanged(availableEndpoints);
+                    } catch (RemoteException ignored) {
+                        Log.d(this, "Remote exception calling onAvailableCallEndpointsChanged");
+                    }
+                }
+            });
+        }
+    }
+
+    @Override
+    public void onMuteStateChanged(boolean isMuted) {
+        if (!mInCallServices.isEmpty()) {
+            Log.i(this, "Calling onMuteStateChanged");
+            mInCallServices.values().forEach(inCallServices -> {
+                for (IInCallService inCallService : inCallServices.values()) {
+                    try {
+                        inCallService.onMuteStateChanged(isMuted);
+                    } catch (RemoteException ignored) {
+                        Log.d(this, "Remote exception calling onMuteStateChanged");
+                    }
+                }
+            });
         }
     }
 
@@ -1334,19 +1598,22 @@
     public void onCanAddCallChanged(boolean canAddCall) {
         if (!mInCallServices.isEmpty()) {
             Log.i(this, "onCanAddCallChanged : %b", canAddCall);
-            for (IInCallService inCallService : mInCallServices.values()) {
-                try {
-                    inCallService.onCanAddCallChanged(canAddCall);
-                } catch (RemoteException ignored) {
+            mInCallServices.values().forEach(inCallServices -> {
+                for (IInCallService inCallService : inCallServices.values()) {
+                    try {
+                        inCallService.onCanAddCallChanged(canAddCall);
+                    } catch (RemoteException ignored) {
+                    }
                 }
-            }
+            });
         }
     }
 
     void onPostDialWait(Call call, String remaining) {
-        if (!mInCallServices.isEmpty()) {
+        UserHandle userFromCall = getUserFromCall(call);
+        if (mInCallServices.containsKey(userFromCall)) {
             Log.i(this, "Calling onPostDialWait, remaining = %s", remaining);
-            for (IInCallService inCallService : mInCallServices.values()) {
+            for (IInCallService inCallService : mInCallServices.get(userFromCall).values()) {
                 try {
                     inCallService.setPostDialWait(mCallIdMapper.getCallId(call), remaining);
                 } catch (RemoteException ignored) {
@@ -1406,6 +1673,7 @@
             }
 
             if (shouldStart) {
+                // Note, not checking return value, as this op call is merely for tracing use
                 mAppOpsManager.startOp(AppOpsManager.OP_PHONE_CALL_CAMERA, myUid(),
                         mContext.getOpPackageName(), false, null, null);
                 mSensorPrivacyManager.showSensorUseDialog(SensorPrivacyManager.Sensors.CAMERA);
@@ -1420,9 +1688,10 @@
         }
     }
 
-    void bringToForeground(boolean showDialpad) {
-        if (!mInCallServices.isEmpty()) {
-            for (IInCallService inCallService : mInCallServices.values()) {
+    @VisibleForTesting
+    public void bringToForeground(boolean showDialpad, UserHandle callingUser) {
+        if (mInCallServices.containsKey(callingUser)) {
+            for (IInCallService inCallService : mInCallServices.get(callingUser).values()) {
                 try {
                     inCallService.bringToForeground(showDialpad);
                 } catch (RemoteException ignored) {
@@ -1433,20 +1702,33 @@
         }
     }
 
-    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;
+    }
+
+    @VisibleForTesting
+    public Map<UserHandle, CarSwappingInCallServiceConnection> getInCallServiceConnections() {
+        return mInCallServiceConnections;
+    }
+
+    void silenceRinger(Set<UserHandle> userHandles) {
+        userHandles.forEach(userHandle -> {
+            if (mInCallServices.containsKey(userHandle)) {
+                for (IInCallService inCallService : mInCallServices.get(userHandle).values()) {
+                    try {
+                        inCallService.silenceRinger();
+                    } catch (RemoteException ignored) {
+                    }
                 }
             }
-        }
+        });
     }
 
     private void notifyConnectionEvent(Call call, String event, Bundle extras) {
-        if (!mInCallServices.isEmpty()) {
-            for (IInCallService inCallService : mInCallServices.values()) {
+        UserHandle userFromCall = getUserFromCall(call);
+        if (mInCallServices.containsKey(userFromCall)) {
+            for (IInCallService inCallService : mInCallServices.get(userFromCall).values()) {
                 try {
                     Log.i(this, "notifyConnectionEvent {Call: %s, Event: %s, Extras:[%s]}",
                             (call != null ? call.toString() : "null"),
@@ -1460,9 +1742,11 @@
     }
 
     private void notifyRttInitiationFailure(Call call, int reason) {
-        if (!mInCallServices.isEmpty()) {
-            mInCallServices.entrySet().stream()
-                    .filter((entry) -> entry.getKey().equals(mInCallServiceConnection.getInfo()))
+        UserHandle userFromCall = getUserFromCall(call);
+        if (mInCallServices.containsKey(userFromCall)) {
+            mInCallServices.get(userFromCall).entrySet().stream()
+                    .filter((entry) -> entry.getKey().equals(mInCallServiceConnections.
+                            get(userFromCall).getInfo()))
                     .forEach((entry) -> {
                         try {
                             Log.i(this, "notifyRttFailure, call %s, incall %s",
@@ -1476,9 +1760,11 @@
     }
 
     private void notifyRemoteRttRequest(Call call, int requestId) {
-        if (!mInCallServices.isEmpty()) {
-            mInCallServices.entrySet().stream()
-                    .filter((entry) -> entry.getKey().equals(mInCallServiceConnection.getInfo()))
+        UserHandle userFromCall = getUserFromCall(call);
+        if (mInCallServices.containsKey(userFromCall)) {
+            mInCallServices.get(userFromCall).entrySet().stream()
+                    .filter((entry) -> entry.getKey().equals(mInCallServiceConnections.
+                            get(userFromCall).getInfo()))
                     .forEach((entry) -> {
                         try {
                             Log.i(this, "notifyRemoteRttRequest, call %s, incall %s",
@@ -1492,8 +1778,9 @@
     }
 
     private void notifyHandoverFailed(Call call, int error) {
-        if (!mInCallServices.isEmpty()) {
-            for (IInCallService inCallService : mInCallServices.values()) {
+        UserHandle userFromCall = getUserFromCall(call);
+        if (mInCallServices.containsKey(userFromCall)) {
+            for (IInCallService inCallService : mInCallServices.get(userFromCall).values()) {
                 try {
                     inCallService.onHandoverFailed(mCallIdMapper.getCallId(call), error);
                 } catch (RemoteException ignored) {
@@ -1503,8 +1790,9 @@
     }
 
     private void notifyHandoverComplete(Call call) {
-        if (!mInCallServices.isEmpty()) {
-            for (IInCallService inCallService : mInCallServices.values()) {
+        UserHandle userFromCall = getUserFromCall(call);
+        if (mInCallServices.containsKey(userFromCall)) {
+            for (IInCallService inCallService : mInCallServices.get(userFromCall).values()) {
                 try {
                     inCallService.onHandoverComplete(mCallIdMapper.getCallId(call));
                 } catch (RemoteException ignored) {
@@ -1516,22 +1804,22 @@
     /**
      * Unbinds an existing bound connection to the in-call app.
      */
-    public void unbindFromServices() {
+    public void unbindFromServices(UserHandle userHandle) {
         try {
             mContext.unregisterReceiver(mPackageChangedReceiver);
         } catch (IllegalArgumentException e) {
             // Ignore this -- we may or may not have registered it, but when we bind, we want to
             // unregister no matter what.
         }
-        if (mInCallServiceConnection != null) {
-            mInCallServiceConnection.disconnect();
-            mInCallServiceConnection = null;
+        if (mInCallServiceConnections.containsKey(userHandle)) {
+            mInCallServiceConnections.get(userHandle).disconnect();
+            mInCallServiceConnections.remove(userHandle);
         }
-        if (mNonUIInCallServiceConnections != null) {
-            mNonUIInCallServiceConnections.disconnect();
-            mNonUIInCallServiceConnections = null;
+        if (mNonUIInCallServiceConnections.containsKey(userHandle)) {
+            mNonUIInCallServiceConnections.get(userHandle).disconnect();
+            mNonUIInCallServiceConnections.remove(userHandle);
         }
-        mInCallServices.clear();
+        mInCallServices.remove(userHandle);
     }
 
     /**
@@ -1543,9 +1831,18 @@
      */
     @VisibleForTesting
     public void bindToServices(Call call) {
-        if (mInCallServiceConnection == null) {
+        UserHandle userFromCall = getUserFromCall(call);
+        UserHandle parentUser = null;
+        UserManager um = mContext.getSystemService(UserManager.class);
+
+        if (um.isManagedProfile(userFromCall.getIdentifier())) {
+            parentUser = um.getProfileParent(userFromCall);
+            Log.i(this, "child:%s  parent:%s", userFromCall, parentUser);
+        }
+
+        if (!mInCallServiceConnections.containsKey(userFromCall)) {
             InCallServiceConnection dialerInCall = null;
-            InCallServiceInfo defaultDialerComponentInfo = getDefaultDialerComponent();
+            InCallServiceInfo defaultDialerComponentInfo = getDefaultDialerComponent(userFromCall);
             Log.i(this, "defaultDialer: " + defaultDialerComponentInfo);
             if (defaultDialerComponentInfo != null &&
                     !defaultDialerComponentInfo.getComponentName().equals(
@@ -1554,28 +1851,44 @@
             }
             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);
+            InCallServiceInfo carModeComponentInfoForParentUser = null;
+            if(parentUser != null) {
+                //query using parent user too
+                carModeComponentInfoForParentUser = getCurrentCarModeComponent(
+                        parentUser);
+            }
+
             if (carModeComponentInfo != null &&
                     !carModeComponentInfo.getComponentName().equals(
                             mDefaultDialerCache.getSystemDialerComponent())) {
                 carModeInCall = new InCallServiceBindingConnection(carModeComponentInfo);
+            } else if (carModeComponentInfo == null &&
+                    carModeComponentInfoForParentUser != null &&
+                    !carModeComponentInfoForParentUser.getComponentName().equals(
+                            mDefaultDialerCache.getSystemDialerComponent())) {
+                carModeInCall = new InCallServiceBindingConnection(
+                        carModeComponentInfoForParentUser, parentUser);
+                Log.i(this, "Using car mode component queried using parent handle");
             }
 
-            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,
@@ -1591,41 +1904,75 @@
 
         IntentFilter packageChangedFilter = new IntentFilter(Intent.ACTION_PACKAGE_CHANGED);
         packageChangedFilter.addDataScheme("package");
-        mContext.registerReceiver(mPackageChangedReceiver, packageChangedFilter);
+        mContext.registerReceiverAsUser(mPackageChangedReceiver, UserHandle.ALL,
+                packageChangedFilter, null, null);
     }
 
-    private void updateNonUiInCallServices() {
+    private void updateNonUiInCallServices(Call call) {
+        UserHandle userFromCall = getUserFromCall(call);
+        UserHandle parentUser = null;
+
+        UserManager um = mContext.getSystemService(UserManager.class);
+        if(um.isManagedProfile(userFromCall.getIdentifier()))
+        {
+            parentUser = um.getProfileParent(userFromCall);
+        }
+
         List<InCallServiceInfo> nonUIInCallComponents =
-                getInCallServiceComponents(IN_CALL_SERVICE_TYPE_NON_UI);
+                getInCallServiceComponents(userFromCall, IN_CALL_SERVICE_TYPE_NON_UI);
+        List<InCallServiceInfo> nonUIInCallComponentsForParent = new ArrayList<>();
+        if(parentUser != null)
+        {
+            //also get Non-UI services using parent handle.
+            nonUIInCallComponentsForParent =
+                    getInCallServiceComponents(parentUser, IN_CALL_SERVICE_TYPE_NON_UI);
+
+        }
         List<InCallServiceBindingConnection> nonUIInCalls = new LinkedList<>();
         for (InCallServiceInfo serviceInfo : nonUIInCallComponents) {
             nonUIInCalls.add(new InCallServiceBindingConnection(serviceInfo));
         }
+
+        //add nonUI InCall services queried using parent user (if any)
+        for (InCallServiceInfo serviceInfo : nonUIInCallComponentsForParent) {
+            if (nonUIInCallComponents.contains(serviceInfo)) {
+                //skip dups
+                Log.i(this, "skipped duplicate component found using parent user: "
+                        + serviceInfo.getComponentName());
+            } else {
+                nonUIInCalls.add(new InCallServiceBindingConnection(serviceInfo, parentUser));
+                Log.i(this,
+                        "added component queried using parent user: "
+                                + serviceInfo.getComponentName());
+            }
+        }
+
         List<String> callCompanionApps = mCallsManager
                 .getRoleManagerAdapter().getCallCompanionApps();
         if (callCompanionApps != null && !callCompanionApps.isEmpty()) {
             for (String pkg : callCompanionApps) {
-                InCallServiceInfo info = getInCallServiceComponent(pkg,
+                InCallServiceInfo info = getInCallServiceComponent(userFromCall, pkg,
                         IN_CALL_SERVICE_TYPE_COMPANION, true /* ignoreDisabled */);
                 if (info != null) {
                     nonUIInCalls.add(new InCallServiceBindingConnection(info));
                 }
             }
         }
-        mNonUIInCallServiceConnections = new NonUIInCallServiceConnectionCollection(
-                nonUIInCalls);
+        mNonUIInCallServiceConnections.put(userFromCall, new NonUIInCallServiceConnectionCollection(
+                nonUIInCalls));
     }
 
     private void connectToNonUiInCallServices(Call call) {
-        if (mNonUIInCallServiceConnections == null) {
-            updateNonUiInCallServices();
+        UserHandle userFromCall = getUserFromCall(call);
+        if (!mNonUIInCallServiceConnections.containsKey(userFromCall)) {
+            updateNonUiInCallServices(call);
         }
-        mNonUIInCallServiceConnections.connect(call);
+        mNonUIInCallServiceConnections.get(userFromCall).connect(call);
     }
 
-    private @Nullable InCallServiceInfo getDefaultDialerComponent() {
+    private @Nullable InCallServiceInfo getDefaultDialerComponent(UserHandle userHandle) {
         String defaultPhoneAppName = mDefaultDialerCache.getDefaultDialerApplication(
-                mCallsManager.getCurrentUserHandle().getIdentifier());
+                userHandle.getIdentifier());
         String systemPhoneAppName = mDefaultDialerCache.getSystemDialerApplication();
 
         Log.d(this, "getDefaultDialerComponent: defaultPhoneAppName=[%s]", defaultPhoneAppName);
@@ -1635,10 +1982,10 @@
         InCallServiceInfo defaultPhoneAppComponent =
                 (systemPhoneAppName != null && systemPhoneAppName.equals(defaultPhoneAppName)) ?
                         /* The defaultPhoneApp is also the systemPhoneApp. Get systemPhoneApp info*/
-                        getInCallServiceComponent(defaultPhoneAppName,
+                        getInCallServiceComponent(userHandle, defaultPhoneAppName,
                                 IN_CALL_SERVICE_TYPE_SYSTEM_UI, true /* ignoreDisabled */)
                         /* The defaultPhoneApp is NOT the systemPhoneApp. Get defaultPhoneApp info*/
-                        : getInCallServiceComponent(defaultPhoneAppName,
+                        : getInCallServiceComponent(userHandle, defaultPhoneAppName,
                                 IN_CALL_SERVICE_TYPE_DEFAULT_DIALER_UI, true /* ignoreDisabled */);
 
         Log.d(this, "getDefaultDialerComponent: defaultPhoneAppComponent=[%s]",
@@ -1650,55 +1997,89 @@
         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 {
             // Last Resort: Try to bind to the ComponentName given directly.
             Log.e(this, new Exception(), "Package Manager could not find ComponentName: "
                     + componentName + ". Trying to bind anyway.");
-            return new InCallServiceInfo(componentName, false, false, type);
+            return new InCallServiceInfo(componentName, false, false, type, false);
         }
     }
 
-    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 boolean canInteractAcrossUsersOrProfiles(ServiceInfo serviceInfo,
+            PackageManager packageManager) {
+        String op = AppOpsManager.permissionToOp("android.permission.INTERACT_ACROSS_PROFILES");
+        String[] uidPackages = packageManager.getPackagesForUid(serviceInfo.applicationInfo.uid);
+
+        boolean hasInteractAcrossProfiles = Arrays.stream(uidPackages).anyMatch(
+                p -> ((packageManager.checkPermission(
+                        Manifest.permission.INTERACT_ACROSS_PROFILES,
+                        p) == PackageManager.PERMISSION_GRANTED)
+                ));
+        boolean hasInteractAcrossUsers = Arrays.stream(uidPackages).anyMatch(
+                p -> ((packageManager.checkPermission(
+                        Manifest.permission.INTERACT_ACROSS_USERS,
+                        p) == PackageManager.PERMISSION_GRANTED)
+                ));
+        boolean hasInteractAcrossProfilesAppOp = Arrays.stream(uidPackages).anyMatch(
+                p -> (AppOpsManager.MODE_ALLOWED == mAppOpsManager.checkOpNoThrow(
+                        op, serviceInfo.applicationInfo.uid, p))
+        );
+        Log.i(this,
+                "packageName:%s INTERACT_ACROSS_USERS:%b INTERACT_ACROSS_PROFILES:%b "
+                        + "INTERACT_ACROSS_PROFILES_APPOP:%b",
+                uidPackages[0], hasInteractAcrossUsers, hasInteractAcrossProfiles,
+                hasInteractAcrossProfilesAppOp);
+
+        return (hasInteractAcrossUsers || hasInteractAcrossProfiles
+                || hasInteractAcrossProfilesAppOp);
     }
 
-    private List<InCallServiceInfo> getInCallServiceComponents(String packageName,
-            ComponentName componentName, int requestedType, boolean ignoreDisabled) {
+    private List<InCallServiceInfo> getInCallServiceComponents(UserHandle userHandle,
+            String packageName, ComponentName componentName,
+            int requestedType, boolean ignoreDisabled) {
         List<InCallServiceInfo> retval = new LinkedList<>();
 
         Intent serviceIntent = new Intent(InCallService.SERVICE_INTERFACE);
@@ -1708,16 +2089,20 @@
         if (componentName != null) {
             serviceIntent.setComponent(componentName);
         }
-
+        Log.i(this,
+                "getComponents, pkgname: " + packageName + " comp: " + componentName + " userid: "
+                        + userHandle.getIdentifier() + " requestedType: " + requestedType);
         PackageManager packageManager = mContext.getPackageManager();
-        Context userContext = mContext.createContextAsUser(mCallsManager.getCurrentUserHandle(),
+        Context userContext = mContext.createContextAsUser(userHandle,
                 0 /* flags */);
         PackageManager userPackageManager = userContext != null ?
                 userContext.getPackageManager() : packageManager;
+
+
         for (ResolveInfo entry : packageManager.queryIntentServicesAsUser(
                 serviceIntent,
                 PackageManager.GET_META_DATA | PackageManager.MATCH_DISABLED_COMPONENTS,
-                mCallsManager.getCurrentUserHandle().getIdentifier())) {
+                userHandle.getIdentifier())) {
             ServiceInfo serviceInfo = entry.serviceInfo;
 
             if (serviceInfo != null) {
@@ -1728,8 +2113,12 @@
                         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);
+
+                boolean hasInteractAcrossUserOrProfilePerm = canInteractAcrossUsersOrProfiles(
+                        entry.serviceInfo, packageManager);
+
                 ComponentName foundComponentName =
                         new ComponentName(serviceInfo.packageName, serviceInfo.name);
                 if (currentType == IN_CALL_SERVICE_TYPE_NON_UI) {
@@ -1745,9 +2134,16 @@
                     isRequestedType = requestedType == currentType;
                 }
 
+                Log.i(this,
+                        "found:%s isRequestedtype:%b isEnabled:%b ignoreDisabled:%b "
+                                + "hasCrossProfilePerm:%b",
+                        foundComponentName, isRequestedType, isEnabled, ignoreDisabled,
+                        hasInteractAcrossUserOrProfilePerm);
+
                 if ((!ignoreDisabled || isEnabled) && isRequestedType) {
                     retval.add(new InCallServiceInfo(foundComponentName, isExternalCallsSupported,
-                            isSelfManageCallsSupported, requestedType));
+                            isSelfManageCallsSupported, requestedType,
+                            hasInteractAcrossUserOrProfilePerm));
                 }
             }
         }
@@ -1780,8 +2176,8 @@
     /**
      * Returns the type of InCallService described by the specified serviceInfo.
      */
-    private int getInCallServiceType(ServiceInfo serviceInfo, PackageManager packageManager,
-            String packageName) {
+    private int getInCallServiceType(UserHandle userHandle, ServiceInfo serviceInfo,
+            PackageManager packageManager, String packageName) {
         // Verify that the InCallService requires the BIND_INCALL_SERVICE permission which
         // enforces that only Telecom can bind to it.
         boolean hasServiceBindPermission = serviceInfo.permission != null &&
@@ -1833,7 +2229,7 @@
         // Check to see that it is the default dialer package
         boolean isDefaultDialerPackage = Objects.equals(serviceInfo.packageName,
                 mDefaultDialerCache.getDefaultDialerApplication(
-                    mCallsManager.getCurrentUserHandle().getIdentifier()));
+                    userHandle.getIdentifier()));
         if (isDefaultDialerPackage && isUIService) {
             return IN_CALL_SERVICE_TYPE_DEFAULT_DIALER_UI;
         }
@@ -1852,11 +2248,11 @@
         return IN_CALL_SERVICE_TYPE_INVALID;
     }
 
-    private void adjustServiceBindingsForEmergency() {
+    private void adjustServiceBindingsForEmergency(UserHandle userHandle) {
         // The connected UI is not the system UI, so lets check if we should switch them
         // if there exists an emergency number.
         if (mCallsManager.isInEmergencyCall()) {
-            mInCallServiceConnection.setHasEmergency(true);
+            mInCallServiceConnections.get(userHandle).setHasEmergency(true);
         }
     }
 
@@ -1869,7 +2265,7 @@
      * @param service The {@link IInCallService} implementation.
      * @return True if we successfully connected.
      */
-    private boolean onConnected(InCallServiceInfo info, IBinder service) {
+    private boolean onConnected(InCallServiceInfo info, IBinder service, UserHandle userHandle) {
         Log.i(this, "onConnected to %s", info.getComponentName());
 
         if (info.getType() == IN_CALL_SERVICE_TYPE_CAR_MODE_UI
@@ -1878,8 +2274,9 @@
             trackCallingUserInterfaceStarted(info);
         }
         IInCallService inCallService = IInCallService.Stub.asInterface(service);
-        mInCallServices.put(info, inCallService);
-
+        mInCallServices.putIfAbsent(userHandle,
+                new ArrayMap<InCallController.InCallServiceInfo, IInCallService>());
+        mInCallServices.get(userHandle).put(info, inCallService);
         try {
             inCallService.setInCallAdapter(
                     new InCallAdapter(
@@ -1889,6 +2286,8 @@
                             info.getComponentName().getPackageName()));
         } catch (RemoteException e) {
             Log.e(this, e, "Failed to set the in-call adapter.");
+            mAnomalyReporter.reportAnomaly(SET_IN_CALL_ADAPTER_ERROR_UUID,
+                    SET_IN_CALL_ADAPTER_ERROR_MSG);
             Trace.endSection();
             return false;
         }
@@ -1924,10 +2323,11 @@
                 return 0;
             }
 
+            UserHandle userFromCall = getUserFromCall(call);
             // Only send the RTT call if it's a UI in-call service
             boolean includeRttCall = false;
-            if (mInCallServiceConnection != null) {
-                includeRttCall = info.equals(mInCallServiceConnection.getInfo());
+            if (mInCallServiceConnections.containsKey(userFromCall)) {
+                includeRttCall = info.equals(mInCallServiceConnections.get(userFromCall).getInfo());
             }
 
             // Track the call if we don't already know about it.
@@ -1953,14 +2353,16 @@
      *
      * @param disconnectedInfo The {@link InCallServiceInfo} of the service which disconnected.
      */
-    private void onDisconnected(InCallServiceInfo disconnectedInfo) {
+    private void onDisconnected(InCallServiceInfo disconnectedInfo, UserHandle userHandle) {
         Log.i(this, "onDisconnected from %s", disconnectedInfo.getComponentName());
         if (disconnectedInfo.getType() == IN_CALL_SERVICE_TYPE_CAR_MODE_UI
                 || disconnectedInfo.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI
                 || disconnectedInfo.getType() == IN_CALL_SERVICE_TYPE_DEFAULT_DIALER_UI) {
             trackCallingUserInterfaceStopped(disconnectedInfo);
         }
-        mInCallServices.remove(disconnectedInfo);
+        if (mInCallServices.containsKey(userHandle)) {
+            mInCallServices.get(userHandle).remove(disconnectedInfo);
+        }
     }
 
     /**
@@ -1969,24 +2371,41 @@
      * @param call The {@link Call}.
      */
     private void updateCall(Call call) {
-        updateCall(call, false /* videoProviderChanged */, false);
+        updateCall(call, false /* videoProviderChanged */, false, null);
     }
 
     /**
      * Informs all {@link InCallService} instances of the updated call information.
      *
-     * @param call The {@link Call}.
+     * @param call                 The {@link Call}.
      * @param videoProviderChanged {@code true} if the video provider changed, {@code false}
-     *      otherwise.
-     * @param rttInfoChanged {@code true} if any information about the RTT session changed,
-     * {@code false} otherwise.
+     *                             otherwise.
+     * @param rttInfoChanged       {@code true} if any information about the RTT session changed,
+     *                             {@code false} otherwise.
+     * @param exceptPackageName    When specified, this package name will not get a call update.
+     *                             Used ONLY from {@link Call#putConnectionServiceExtras(int, Bundle, String)} to
+     *                             ensure we can propagate extras changes between InCallServices but
+     *                             not inform the requestor of their own change.
      */
-    private void updateCall(Call call, boolean videoProviderChanged, boolean rttInfoChanged) {
-        if (!mInCallServices.isEmpty()) {
+    private void updateCall(Call call, boolean videoProviderChanged, boolean rttInfoChanged,
+            String exceptPackageName) {
+        UserHandle userFromCall = getUserFromCall(call);
+        if (mInCallServices.containsKey(userFromCall)) {
             Log.i(this, "Sending updateCall %s", call);
             List<ComponentName> componentsUpdated = new ArrayList<>();
-            for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
+            for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.
+                    get(userFromCall).entrySet()) {
                 InCallServiceInfo info = entry.getKey();
+                ComponentName componentName = info.getComponentName();
+
+                // If specified, skip ICS if it matches the package name.  Used for cases where on
+                // ICS makes an update to extras and we want to skip updating the same ICS with the
+                // change that it implemented.
+                if (exceptPackageName != null
+                        && componentName.getPackageName().equals(exceptPackageName)) {
+                    continue;
+                }
+
                 if (call.isExternalCall() && !info.isExternalCallsSupported()) {
                     continue;
                 }
@@ -2001,10 +2420,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);
 
@@ -2022,7 +2441,8 @@
      * Adds the call to the list of calls tracked by the {@link InCallController}.
      * @param call The call to add.
      */
-    private void addCall(Call call) {
+    @VisibleForTesting
+    public void addCall(Call call) {
         if (mCallIdMapper.getCalls().size() == 0) {
             mAppOpsManager.startWatchingActive(new String[] { OPSTR_RECORD_AUDIO },
                     java.lang.Runnable::run, this);
@@ -2041,8 +2461,11 @@
     /**
      * @return true if we are bound to the UI InCallService and it is connected.
      */
-    private boolean isBoundAndConnectedToServices() {
-        return mInCallServiceConnection != null && mInCallServiceConnection.isConnected();
+    private boolean isBoundAndConnectedToServices(UserHandle userHandle) {
+        if (!mInCallServiceConnections.containsKey(userHandle)) {
+            return false;
+        }
+        return mInCallServiceConnections.get(userHandle).isConnected();
     }
 
     /**
@@ -2061,15 +2484,17 @@
     public void dump(IndentingPrintWriter pw) {
         pw.println("mInCallServices (InCalls registered):");
         pw.increaseIndent();
-        for (InCallServiceInfo info : mInCallServices.keySet()) {
-            pw.println(info);
-        }
+        mInCallServices.values().forEach(inCallServices -> {
+            for (InCallServiceInfo info : inCallServices.keySet()) {
+                pw.println(info);
+            }
+        });
         pw.decreaseIndent();
 
         pw.println("ServiceConnections (InCalls bound):");
         pw.increaseIndent();
-        if (mInCallServiceConnection != null) {
-            mInCallServiceConnection.dump(pw);
+        for (InCallServiceConnection inCallServiceConnection : mInCallServiceConnections.values()) {
+            inCallServiceConnection.dump(pw);
         }
         pw.decreaseIndent();
 
@@ -2079,22 +2504,25 @@
     /**
      * @return The package name of the UI which is currently bound, or null if none.
      */
-    private ComponentName getConnectedUi() {
-        InCallServiceInfo connectedUi = mInCallServices.keySet().stream().filter(
-                i -> i.getType() == IN_CALL_SERVICE_TYPE_DEFAULT_DIALER_UI
-                        || i.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI)
-                .findAny()
-                .orElse(null);
-        if (connectedUi != null) {
-            return connectedUi.mComponentName;
+    private ComponentName getConnectedUi(UserHandle userHandle) {
+        if (mInCallServices.containsKey(userHandle)) {
+            InCallServiceInfo connectedUi = mInCallServices.get(
+                            userHandle).keySet().stream().filter(
+                            i -> i.getType() == IN_CALL_SERVICE_TYPE_DEFAULT_DIALER_UI
+                                    || i.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI)
+                    .findAny()
+                    .orElse(null);
+            if (connectedUi != null) {
+                return connectedUi.mComponentName;
+            }
         }
         return null;
     }
 
-    public boolean doesConnectedDialerSupportRinging() {
+    public boolean doesConnectedDialerSupportRinging(UserHandle userHandle) {
         String ringingPackage =  null;
 
-        ComponentName connectedPackage = getConnectedUi();
+        ComponentName connectedPackage = getConnectedUi(userHandle);
         if (connectedPackage != null) {
             ringingPackage = connectedPackage.getPackageName().trim();
             Log.d(this, "doesConnectedDialerSupportRinging: alreadyConnectedPackage=%s",
@@ -2104,7 +2532,7 @@
         if (TextUtils.isEmpty(ringingPackage)) {
             // The current in-call UI returned nothing, so lets use the default dialer.
             ringingPackage = mDefaultDialerCache.getRoleManagerAdapter().getDefaultDialerApp(
-                    mCallsManager.getCurrentUserHandle().getIdentifier());
+                    userHandle.getIdentifier());
             if (ringingPackage != null) {
                 Log.d(this, "doesConnectedDialerSupportRinging: notCurentlyConnectedPackage=%s",
                         ringingPackage);
@@ -2119,7 +2547,7 @@
             .setPackage(ringingPackage);
         List<ResolveInfo> entries = mContext.getPackageManager().queryIntentServicesAsUser(
                 intent, PackageManager.GET_META_DATA,
-                mCallsManager.getCurrentUserHandle().getIdentifier());
+                userHandle.getIdentifier());
         if (entries.isEmpty()) {
             Log.w(this, "doesConnectedDialerSupportRinging: couldn't find dialer's package info"
                     + " <sad trombone>");
@@ -2151,15 +2579,32 @@
         return childCalls;
     }
 
-    private ParcelableCall sanitizeParcelableCallForService(
+    @VisibleForTesting
+    public ParcelableCall sanitizeParcelableCallForService(
             InCallServiceInfo info, ParcelableCall parcelableCall) {
         ParcelableCall.ParcelableCallBuilder builder =
                 ParcelableCall.ParcelableCallBuilder.fromParcelableCall(parcelableCall);
-        // Check for contacts permission. If it's not there, remove the contactsDisplayName.
         PackageManager pm = mContext.getPackageManager();
+
+        // Check for contacts permission.
         if (pm.checkPermission(Manifest.permission.READ_CONTACTS,
                 info.getComponentName().getPackageName()) != PackageManager.PERMISSION_GRANTED) {
+            // contacts permission is not present...
+
+            // removing the contactsDisplayName
             builder.setContactDisplayName(null);
+            builder.setContactPhotoUri(null);
+
+            // removing the Call.EXTRA_IS_SUPPRESSED_BY_DO_NOT_DISTURB extra
+            if (parcelableCall.getExtras() != null) {
+                Bundle callBundle = parcelableCall.getExtras();
+                if (callBundle.containsKey(
+                        android.telecom.Call.EXTRA_IS_SUPPRESSED_BY_DO_NOT_DISTURB)) {
+                    Bundle newBundle = callBundle.deepCopy();
+                    newBundle.remove(android.telecom.Call.EXTRA_IS_SUPPRESSED_BY_DO_NOT_DISTURB);
+                    builder.setExtras(newBundle);
+                }
+            }
         }
 
         // TODO: move all the other service-specific sanitizations in here
@@ -2181,8 +2626,8 @@
         // Disabled InCallService should also be considered as a valid InCallService here so that
         // it can be added to the CarModeTracker, in case it will be enabled in future.
         InCallServiceInfo info =
-                getInCallServiceComponent(packageName, IN_CALL_SERVICE_TYPE_CAR_MODE_UI,
-                        false /* ignoreDisabled */);
+                getInCallServiceComponent(mCallsManager.getCurrentUserHandle(),
+                        packageName, IN_CALL_SERVICE_TYPE_CAR_MODE_UI, false /* ignoreDisabled */);
         return info != null && info.getType() == IN_CALL_SERVICE_TYPE_CAR_MODE_UI;
     }
 
@@ -2231,17 +2676,49 @@
     public void updateCarModeForConnections() {
         Log.i(this, "updateCarModeForConnections: car mode apps: %s",
                 mCarModeTracker.getCarModeApps().stream().collect(Collectors.joining(", ")));
-        if (mInCallServiceConnection != null) {
-            if (shouldUseCarModeUI()) {
-                Log.i(this, "updateCarModeForConnections: potentially update car mode app.");
-                mInCallServiceConnection.changeCarModeApp(
-                        mCarModeTracker.getCurrentCarModePackage());
-            } else {
-                if (mInCallServiceConnection.isCarMode()) {
-                    Log.i(this, "updateCarModeForConnections: car mode no longer "
-                            + "applicable; disabling");
-                    mInCallServiceConnection.disableCarMode();
-                }
+
+        UserManager um = mContext.getSystemService(UserManager.class);
+        UserHandle currentUser = mCallsManager.getCurrentUserHandle();
+        UserHandle childUser = findChildManagedProfileUser(currentUser, um);
+
+        CarSwappingInCallServiceConnection inCallServiceConnectionForCurrentUser = null;
+        CarSwappingInCallServiceConnection inCallServiceConnectionForChildUser = null;
+
+        Log.i(this, "update carmode current:%s parent:%s", currentUser, childUser);
+        if (mInCallServiceConnections.containsKey(currentUser)) {
+            inCallServiceConnectionForCurrentUser = mInCallServiceConnections.
+                    get(currentUser);
+        }
+        if (childUser != null && mInCallServiceConnections.containsKey(childUser)) {
+            inCallServiceConnectionForChildUser = mInCallServiceConnections.
+                    get(childUser);
+        }
+
+        if (shouldUseCarModeUI()) {
+            Log.i(this, "updateCarModeForConnections: potentially update car mode app.");
+            //always pass current user to changeCarMode. That will ultimately be used for bindAsUser
+            if (inCallServiceConnectionForCurrentUser != null) {
+                inCallServiceConnectionForCurrentUser.changeCarModeApp(
+                        mCarModeTracker.getCurrentCarModePackage(),
+                        currentUser);
+            }
+            if (inCallServiceConnectionForChildUser != null) {
+                inCallServiceConnectionForChildUser.changeCarModeApp(
+                        mCarModeTracker.getCurrentCarModePackage(),
+                        currentUser);
+            }
+        } else {
+            if (inCallServiceConnectionForCurrentUser != null
+                    && inCallServiceConnectionForCurrentUser.isCarMode()) {
+                Log.i(this, "updateCarModeForConnections: car mode no longer "
+                        + "applicable for current user; disabling");
+                inCallServiceConnectionForCurrentUser.disableCarMode();
+            }
+            if (inCallServiceConnectionForChildUser != null
+                    && inCallServiceConnectionForChildUser.isCarMode()) {
+                Log.i(this, "updateCarModeForConnections: car mode no longer "
+                        + "applicable for child user; disabling");
+                inCallServiceConnectionForChildUser.disableCarMode();
             }
         }
     }
@@ -2303,6 +2780,7 @@
                 && !isCarrierPrivilegedUsingMicDuringVoipCall();
         if (wasUsingMicrophone != mIsCallUsingMicrophone) {
             if (mIsCallUsingMicrophone) {
+                // Note, not checking return value, as this op call is merely for tracing use
                 mAppOpsManager.startOp(AppOpsManager.OP_PHONE_CALL_MICROPHONE, myUid(),
                         mContext.getOpPackageName(), false, null, null);
                 mSensorPrivacyManager.showSensorUseDialog(SensorPrivacyManager.Sensors.MICROPHONE);
@@ -2348,7 +2826,7 @@
                                                         == PermissionChecker.PERMISSION_GRANTED;
     }
 
-    private void sendCrashedInCallServiceNotification(String packageName) {
+    private void sendCrashedInCallServiceNotification(String packageName, UserHandle userHandle) {
         PackageManager packageManager = mContext.getPackageManager();
         CharSequence appName;
         String systemDialer = mDefaultDialerCache.getSystemDialerApplication();
@@ -2376,8 +2854,8 @@
                 .setStyle(new Notification.BigTextStyle()
                         .bigText(mContext.getText(
                                 R.string.notification_incallservice_not_responding_body)));
-        notificationManager.notify(NOTIFICATION_TAG, IN_CALL_SERVICE_NOTIFICATION_ID,
-                builder.build());
+        notificationManager.notifyAsUser(NOTIFICATION_TAG, IN_CALL_SERVICE_NOTIFICATION_ID,
+                builder.build(), userHandle);
     }
 
     private void updateCallTracking(Call call, InCallServiceInfo info, boolean isAdd) {
@@ -2386,4 +2864,21 @@
                 || 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.getAssociatedUser();
+            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() || call.isInECBM())
+                    && userManager.isQuietModeEnabled(userFromCall)) {
+                return mCallsManager.getCurrentUserHandle();
+            }
+            return userFromCall;
+        }
+    }
 }
diff --git a/src/com/android/server/telecom/InCallTonePlayer.java b/src/com/android/server/telecom/InCallTonePlayer.java
index 0367ba0..3cc4aac 100644
--- a/src/com/android/server/telecom/InCallTonePlayer.java
+++ b/src/com/android/server/telecom/InCallTonePlayer.java
@@ -170,7 +170,7 @@
 
     private static final int RELATIVE_VOLUME_EMERGENCY = 100;
     private static final int RELATIVE_VOLUME_HIPRI = 80;
-    private static final int RELATIVE_VOLUME_LOPRI = 50;
+    private static final int RELATIVE_VOLUME_LOPRI = 30;
     private static final int RELATIVE_VOLUME_UNDEFINED = -1;
 
     // Buffer time (in msec) to add on to the tone timeout value. Needed mainly when the timeout
@@ -470,12 +470,6 @@
 
     @VisibleForTesting
     public boolean startTone() {
-        // Skip playing the end call tone if the volume is silenced.
-        if (mToneId == TONE_CALL_ENDED && !mAudioManagerAdapter.isVolumeOverZero()) {
-            Log.i(this, "startTone: skip end-call tone as device is silenced.");
-            return false;
-        }
-
         // Tone already done; don't allow re-used
         if (mState == STATE_STOPPED) {
             return false;
diff --git a/src/com/android/server/telecom/LogUtils.java b/src/com/android/server/telecom/LogUtils.java
index 12e780f..0d6acd5 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,15 @@
                 "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 final String GAINED_FGS_DELEGATION = "GAINED_FGS_DELEGATION";
+        public static final String GAIN_FGS_DELEGATION_FAILED = "GAIN_FGS_DELEGATION_FAILED";
+        public static final String LOST_FGS_DELEGATION = "LOST_FGS_DELEGATION";
+        public static final String START_STREAMING = "START_STREAMING";
+        public static final String STOP_STREAMING = "STOP_STREAMING";
 
         public static class Timings {
             public static final String ACCEPT_TIMING = "accept";
@@ -222,6 +241,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 +263,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/MmiUtils.java b/src/com/android/server/telecom/MmiUtils.java
new file mode 100644
index 0000000..11f6d59
--- /dev/null
+++ b/src/com/android/server/telecom/MmiUtils.java
@@ -0,0 +1,184 @@
+/*
+ * 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.net.Uri;
+import android.telecom.PhoneAccount;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class MmiUtils {
+    // See TS 22.030 6.5.2 "Structure of the MMI"
+
+    private static Pattern sPatternSuppService = Pattern.compile(
+            "((\\*|#|\\*#|\\*\\*|##)(\\d{2,3})(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*))?)?)?)?#)(.*)");
+        /*       1  2                    3          4  5       6   7         8    9     10  11
+              12
+
+                 1 = Full string up to and including #
+                 2 = action (activation/interrogation/registration/erasure)
+                 3 = service code
+                 5 = SIA
+                 7 = SIB
+                 9 = SIC
+                 10 = dialing number
+        */
+    //regex groups
+    static final int MATCH_GROUP_POUND_STRING = 1;
+    static final int MATCH_GROUP_ACTION = 2; //(activation/interrogation/registration/erasure)
+    static final int MATCH_GROUP_SERVICE_CODE = 3;
+    static final int MATCH_GROUP_SIA = 5;
+    static final int MATCH_GROUP_SIB = 7;
+    static final int MATCH_GROUP_SIC = 9;
+    static final int MATCH_GROUP_PWD_CONFIRM = 11;
+    static final int MATCH_GROUP_DIALING_NUMBER = 12;
+    // Call Forwarding service codes
+    static final String SC_CFU = "21";
+    static final String SC_CFB = "67";
+    static final String SC_CFNRy = "61";
+    static final String SC_CFNR = "62";
+    static final String SC_CF_All = "002";
+    static final String SC_CF_All_Conditional = "004";
+
+    //see: https://nationalnanpa.com/number_resource_info/vsc_assignments.html
+    @SuppressWarnings("DoubleBraceInitialization")
+    private static Set<String> sDangerousVerticalServiceCodes = new HashSet<String>()
+    {{
+        add("*09"); //Selective Call Blocking/Reporting
+        add("*42"); //Change Forward-To Number for Cust Programmable Call Forwarding Don't Answer
+        add("*56"); //Change Forward-To Number for ISDN Call Forwarding
+        add("*60"); //Selective Call Rejection Activation
+        add("*63"); //Selective Call Forwarding Activation
+        add("*64"); //Selective Call Acceptance Activation
+        add("*68"); //Call Forwarding Busy Line/Don't Answer Activation
+        add("*72"); //Call Forwarding Activation
+        add("*77"); //Anonymous Call Rejection Activation
+        add("*78"); //Do Not Disturb Activation
+    }};
+    private final int mMinLenInDangerousSet;
+    private final int mMaxLenInDangerousSet;
+
+    public MmiUtils() {
+        mMinLenInDangerousSet = sDangerousVerticalServiceCodes.stream()
+                .mapToInt(String::length)
+                .min()
+                .getAsInt();
+        mMaxLenInDangerousSet = sDangerousVerticalServiceCodes.stream()
+                .mapToInt(String::length)
+                .max()
+                .getAsInt();
+    }
+
+    /**
+     * Determines if the Uri represents a call forwarding related mmi code
+     *
+     * @param handle The URI to call.
+     * @return {@code True} if the URI represents a call forwarding related MMI
+     */
+    private static boolean isCallForwardingMmiCode(Uri handle) {
+        Matcher m;
+        String dialString = handle.getSchemeSpecificPart();
+        m = sPatternSuppService.matcher(dialString);
+
+        if (m.matches()) {
+            String sc = m.group(MATCH_GROUP_SERVICE_CODE);
+            return sc != null &&
+                    (sc.equals(SC_CFU)
+                            || sc.equals(SC_CFB) || sc.equals(SC_CFNRy)
+                            || sc.equals(SC_CFNR) || sc.equals(SC_CF_All)
+                            || sc.equals(SC_CF_All_Conditional));
+        }
+
+        return false;
+
+    }
+
+    private static boolean isTelScheme(Uri handle) {
+        return (handle != null && handle.getSchemeSpecificPart() != null &&
+                handle.getScheme() != null &&
+                handle.getScheme().equals(PhoneAccount.SCHEME_TEL));
+    }
+
+    private boolean isDangerousVerticalServiceCode(Uri handle) {
+        if (isTelScheme(handle)) {
+            String dialedNumber = handle.getSchemeSpecificPart();
+            if (dialedNumber.length() >= mMinLenInDangerousSet && dialedNumber.charAt(0) == '*') {
+                //we only check vertical codes defined by The North American Numbering Plan Admin
+                //see: https://nationalnanpa.com/number_resource_info/vsc_assignments.html
+                //only two or 3-digit codes are valid as of today, but the code is generic enough.
+                for (int prefixLen = mMaxLenInDangerousSet; prefixLen <= mMaxLenInDangerousSet;
+                        prefixLen++) {
+                    String prefix = dialedNumber.substring(0, prefixLen);
+                    if (sDangerousVerticalServiceCodes.contains(prefix)) {
+                        return true;
+                    }
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Determines if a dialed number is potentially an In-Call MMI code.  In-Call MMI codes are
+     * MMI codes which can be dialed when one or more calls are in progress.
+     * <P>
+     * Checks for numbers formatted similar to the MMI codes defined in:
+     * {@link com.android.internal.telephony.Phone#handleInCallMmiCommands(String)}
+     *
+     * @param handle The URI to call.
+     * @return {@code True} if the URI represents a number which could be an in-call MMI code.
+     */
+    public boolean isPotentialInCallMMICode(Uri handle) {
+        if (isTelScheme(handle)) {
+            String dialedNumber = handle.getSchemeSpecificPart();
+            return (dialedNumber.equals("0") ||
+                    (dialedNumber.startsWith("1") && dialedNumber.length() <= 2) ||
+                    (dialedNumber.startsWith("2") && dialedNumber.length() <= 2) ||
+                    dialedNumber.equals("3") ||
+                    dialedNumber.equals("4") ||
+                    dialedNumber.equals("5"));
+        }
+        return false;
+    }
+
+    public boolean isPotentialMMICode(Uri handle) {
+        return (handle != null && handle.getSchemeSpecificPart() != null
+                && handle.getSchemeSpecificPart().contains("#"));
+    }
+
+    /**
+     * Determines if the Uri represents a dangerous MMI code or Vertical Service code. Dangerous
+     * codes are ones, for which,
+     * we normally expect the user to be aware that an application has dialed them
+     *
+     * @param handle The URI to call.
+     * @return {@code True} if the URI represents a dangerous code
+     */
+    public boolean isDangerousMmiOrVerticalCode(Uri handle) {
+        if (isPotentialMMICode(handle)) {
+            return isCallForwardingMmiCode(handle);
+            //since some dangerous mmi codes could be carrier specific, in the future,
+            //we can add a carrier config item which can list carrier specific dangerous mmi codes
+        } else if (isDangerousVerticalServiceCode(handle)) {
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
index 4569950..3b402b1 100644
--- a/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
+++ b/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
@@ -16,14 +16,12 @@
 
 package com.android.server.telecom;
 
-import android.app.AppOpsManager;
-
 import android.app.Activity;
+import android.app.AppOpsManager;
 import android.app.BroadcastOptions;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.content.res.Resources;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Trace;
@@ -78,6 +76,7 @@
     private final PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter;
     private final TelecomSystem.SyncRoot mLock;
     private final DefaultDialerCache mDefaultDialerCache;
+    private final MmiUtils mMmiUtils;
 
     /*
      * Whether or not the outgoing call intent originated from the default phone application. If
@@ -101,7 +100,7 @@
     @VisibleForTesting
     public NewOutgoingCallIntentBroadcaster(Context context, CallsManager callsManager,
             Intent intent, PhoneNumberUtilsAdapter phoneNumberUtilsAdapter,
-            boolean isDefaultPhoneApp, DefaultDialerCache defaultDialerCache) {
+            boolean isDefaultPhoneApp, DefaultDialerCache defaultDialerCache, MmiUtils mmiUtils) {
         mContext = context;
         mCallsManager = callsManager;
         mIntent = intent;
@@ -109,6 +108,7 @@
         mIsDefaultOrSystemPhoneApp = isDefaultPhoneApp;
         mLock = mCallsManager.getLock();
         mDefaultDialerCache = defaultDialerCache;
+        mMmiUtils = mmiUtils;
     }
 
     /**
@@ -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);
@@ -291,9 +291,19 @@
                     result.callImmediately = true;
                     result.requestRedirection = false;
                 }
+            } else if (mMmiUtils.isDangerousMmiOrVerticalCode(intent.getData())) {
+                if (!mIsDefaultOrSystemPhoneApp) {
+                    Log.w(this,
+                            "Potentially dangerous MMI code %s with CALL Intent %s can only be "
+                                    + "sent if caller is the system or default dialer",
+                            number, intent);
+                    launchSystemDialer(intent.getData());
+                    result.disconnectCause = DisconnectCause.OUTGOING_CANCELED;
+                    return result;
+                }
             }
         } 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,14 +382,14 @@
              * broadcasting.
              */
             callRedirectionWithService = callRedirectionProcessor
-                    .canMakeCallRedirectionWithService();
+                    .canMakeCallRedirectionWithServiceAsUser(mCall.getAssociatedUser());
             if (callRedirectionWithService) {
-                callRedirectionProcessor.performCallRedirection();
+                callRedirectionProcessor.performCallRedirection(mCall.getAssociatedUser());
             }
         }
 
         if (disposition.sendBroadcast) {
-            UserHandle targetUser = mCall.getInitiatingUser();
+            UserHandle targetUser = mCall.getAssociatedUser();
             Log.i(this, "Sending NewOutgoingCallBroadcast for %s to %s", mCall, targetUser);
             broadcastIntent(mIntent, disposition.number,
                     !disposition.callImmediately && !callRedirectionWithService, targetUser);
@@ -517,23 +527,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 +548,17 @@
      * the appropriate call intent action.
      *
      * @param intent Intent to evaluate
-     * @param isPotentialEmergencyNumber Whether or not the number is potentially an emergency
+     * @param isEmergencyNumber Whether or not the number is an emergency
      * number.
      * @return The appropriate action.
      */
-    private String calculateCallIntentAction(Intent intent, boolean isPotentialEmergencyNumber) {
+    private String calculateCallIntentAction(Intent intent, boolean isEmergencyNumber) {
         String action = intent.getAction();
 
         /* Change CALL_PRIVILEGED into CALL or CALL_EMERGENCY as needed. */
         if (Intent.ACTION_CALL_PRIVILEGED.equals(action)) {
-            if (isPotentialEmergencyNumber) {
-                Log.i(this, "ACTION_CALL_PRIVILEGED is used while the number is a potential"
+            if (isEmergencyNumber) {
+                Log.i(this, "ACTION_CALL_PRIVILEGED is used while the number is a"
                         + " emergency number. Using ACTION_CALL_EMERGENCY as an action instead.");
                 action = Intent.ACTION_CALL_EMERGENCY;
             } else {
diff --git a/src/com/android/server/telecom/ParcelableCallUtils.java b/src/com/android/server/telecom/ParcelableCallUtils.java
index 0becaef..673b99a 100644
--- a/src/com/android/server/telecom/ParcelableCallUtils.java
+++ b/src/com/android/server/telecom/ParcelableCallUtils.java
@@ -196,6 +196,8 @@
         String callerDisplayName = call.getCallerDisplayNamePresentation() ==
                 TelecomManager.PRESENTATION_ALLOWED ?  call.getCallerDisplayName() : null;
 
+        Uri contactPhotoUri = call.getContactPhotoUri();
+
         List<Call> conferenceableCalls = call.getConferenceableCalls();
         List<String> conferenceableCallIds = new ArrayList<String>(conferenceableCalls.size());
         for (Call otherCall : conferenceableCalls) {
@@ -255,6 +257,7 @@
                 .setCallerNumberVerificationStatus(call.getCallerNumberVerificationStatus())
                 .setContactDisplayName(call.getName())
                 .setActiveChildCallId(activeChildCallId)
+                .setContactPhotoUri(contactPhotoUri)
                 .createParcelableCall();
     }
 
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index 576a289..acf07e3 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";
@@ -204,6 +205,7 @@
 
         // register context based receiver to clean up orphan phone accounts
         IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MANAGED_PROFILE_REMOVED);
+        intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
         mContext.registerReceiver(mManagedProfileReceiver, intentFilter);
 
         read();
@@ -248,7 +250,7 @@
         }
 
         List<PhoneAccountHandle> outgoing = getCallCapablePhoneAccounts(uriScheme, false,
-                userHandle);
+                userHandle, false);
         switch (outgoing.size()) {
             case 0:
                 // There are no accounts, so there can be no default
@@ -323,7 +325,7 @@
         }
         // Get the PhoneAccount with the same group Id (and same ComponentName) that is not the
         // newAccount that was just added
-        List<PhoneAccount> accounts = getAllPhoneAccounts(userHandle).stream()
+        List<PhoneAccount> accounts = getAllPhoneAccounts(userHandle, false).stream()
                 .filter(account -> groupId.equals(account.getGroupId()) &&
                         !account.getAccountHandle().equals(excludePhoneAccountHandle) &&
                         Objects.equals(account.getAccountHandle().getComponentName(),
@@ -469,7 +471,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();
 
@@ -561,10 +563,7 @@
         if (call == null) {
             return null;
         }
-        UserHandle userHandle = call.getInitiatingUser();
-        if (userHandle == null) {
-            userHandle = call.getTargetPhoneAccount().getUserHandle();
-        }
+        UserHandle userHandle = call.getAssociatedUser();
         PhoneAccountHandle targetPhoneAccount = call.getTargetPhoneAccount();
         Log.d(this, "getSimCallManagerFromCall: callId=%s, targetPhac=%s",
                 call.getId(), targetPhoneAccount);
@@ -725,16 +724,13 @@
      *
      * @return The list of {@link PhoneAccountHandle}s.
      */
-    public List<PhoneAccountHandle> getAllPhoneAccountHandles(UserHandle userHandle) {
-        return getPhoneAccountHandles(0, null, null, false, userHandle);
+    public List<PhoneAccountHandle> getAllPhoneAccountHandles(UserHandle userHandle,
+            boolean crossUserAccess) {
+        return getPhoneAccountHandles(0, null, null, false, userHandle, crossUserAccess);
     }
 
-    public List<PhoneAccount> getAllPhoneAccounts(UserHandle userHandle) {
-        return getPhoneAccounts(0, null, null, false, userHandle);
-    }
-
-    public List<PhoneAccount> getAllPhoneAccountsOfCurrentUser() {
-        return getAllPhoneAccounts(mCurrentUserHandle);
+    public List<PhoneAccount> getAllPhoneAccounts(UserHandle userHandle, boolean crossUserAccess) {
+        return getPhoneAccounts(0, null, null, false, mCurrentUserHandle, crossUserAccess);
     }
 
     /**
@@ -748,9 +744,11 @@
      * @return The phone account handles.
      */
     public List<PhoneAccountHandle> getCallCapablePhoneAccounts(
-            String uriScheme, boolean includeDisabledAccounts, UserHandle userHandle) {
+            String uriScheme, boolean includeDisabledAccounts,
+            UserHandle userHandle, boolean crossUserAccess) {
         return getCallCapablePhoneAccounts(uriScheme, includeDisabledAccounts, userHandle,
-                0 /* capabilities */, PhoneAccount.CAPABILITY_EMERGENCY_CALLS_ONLY);
+                0 /* capabilities */, PhoneAccount.CAPABILITY_EMERGENCY_CALLS_ONLY,
+                crossUserAccess);
     }
 
     /**
@@ -767,11 +765,11 @@
      */
     public List<PhoneAccountHandle> getCallCapablePhoneAccounts(
             String uriScheme, boolean includeDisabledAccounts, UserHandle userHandle,
-            int capabilities, int excludedCapabilities) {
+            int capabilities, int excludedCapabilities, boolean crossUserAccess) {
         return getPhoneAccountHandles(
                 PhoneAccount.CAPABILITY_CALL_PROVIDER | capabilities,
                 excludedCapabilities /*excludedCapabilities*/,
-                uriScheme, null, includeDisabledAccounts, userHandle);
+                uriScheme, null, includeDisabledAccounts, userHandle, crossUserAccess);
     }
 
     /**
@@ -789,12 +787,7 @@
                 PhoneAccount.CAPABILITY_SELF_MANAGED,
                 PhoneAccount.CAPABILITY_EMERGENCY_CALLS_ONLY /* excludedCapabilities */,
                 null /* uriScheme */, null /* packageName */, false /* includeDisabledAccounts */,
-                userHandle);
-    }
-
-    public List<PhoneAccountHandle> getCallCapablePhoneAccountsOfCurrentUser(
-            String uriScheme, boolean includeDisabledAccounts) {
-        return getCallCapablePhoneAccounts(uriScheme, includeDisabledAccounts, mCurrentUserHandle);
+                userHandle, false);
     }
 
     /**
@@ -803,7 +796,7 @@
     public List<PhoneAccountHandle> getSimPhoneAccounts(UserHandle userHandle) {
         return getPhoneAccountHandles(
                 PhoneAccount.CAPABILITY_CALL_PROVIDER | PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION,
-                null, null, false, userHandle);
+                null, null, false, userHandle, false);
     }
 
     public List<PhoneAccountHandle> getSimPhoneAccountsOfCurrentUser() {
@@ -818,7 +811,17 @@
          */
     public List<PhoneAccountHandle> getPhoneAccountsForPackage(String packageName,
             UserHandle userHandle) {
-        return getPhoneAccountHandles(0, null, packageName, false, userHandle);
+        return getPhoneAccountHandles(0, null, packageName, false, userHandle, false);
+    }
+
+
+    /**
+     * includes disabled, includes crossUserAccess
+     */
+    public List<PhoneAccountHandle> getAllPhoneAccountHandlesForPackage(UserHandle userHandle,
+            String packageName) {
+        return getPhoneAccountHandles(0, null, packageName, true /* includeDisabled */, userHandle,
+                true /* crossUserAccess */);
     }
 
     /**
@@ -859,34 +862,190 @@
      * Performs checks before calling addOrReplacePhoneAccount(PhoneAccount)
      *
      * @param account The {@code PhoneAccount} to add or replace.
-     * @throws SecurityException if package does not have BIND_TELECOM_CONNECTION_SERVICE permission
+     * @throws SecurityException        if package does not have BIND_TELECOM_CONNECTION_SERVICE
+     *                                  permission
      * @throws IllegalArgumentException if MAX_PHONE_ACCOUNT_REGISTRATIONS are reached
+     * @throws IllegalArgumentException if MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT is reached
+     * @throws IllegalArgumentException if writing the Icon to memory will cause an Exception
      */
     public void registerPhoneAccount(PhoneAccount account) {
         // Enforce the requirement that a connection service for a phone account has the correct
         // permission.
-        if (!phoneAccountRequiresBindPermission(account.getAccountHandle())) {
+        if (!hasTransactionalCallCapabilities(account) &&
+                !phoneAccountRequiresBindPermission(account.getAccountHandle())) {
             Log.w(this,
                     "Phone account %s does not have BIND_TELECOM_CONNECTION_SERVICE permission.",
                     account.getAccountHandle());
-            throw new SecurityException("PhoneAccount connection service requires "
-                    + "BIND_TELECOM_CONNECTION_SERVICE permission.");
+            throw new SecurityException("Registering a PhoneAccount requires either: "
+                    + "(1) The Service definition requires that the ConnectionService is guarded"
+                    + " with the BIND_TELECOM_CONNECTION_SERVICE, which can be defined using the"
+                    + " android:permission tag as part of the Service definition. "
+                    + "(2) The PhoneAccount capability called"
+                    + " CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS.");
         }
-        //Enforce an upper bound on the number of PhoneAccount's a package can register.
-        // Most apps should only require 1-2.
-        if (getPhoneAccountsForPackage(
-                account.getAccountHandle().getComponentName().getPackageName(),
-                account.getAccountHandle().getUserHandle()).size()
+        enforceCharacterLimit(account);
+        enforceIconSizeLimit(account);
+        enforceMaxPhoneAccountLimit(account);
+        addOrReplacePhoneAccount(account);
+    }
+
+    /**
+     * Enforce an upper bound on the number of PhoneAccount's a package can register.
+     * Most apps should only require 1-2.  * Include disabled accounts.
+     *
+     * @param account to enforce check on
+     * @throws IllegalArgumentException if MAX_PHONE_ACCOUNT_REGISTRATIONS are reached
+     */
+    private void enforceMaxPhoneAccountLimit(@NonNull PhoneAccount account) {
+        final PhoneAccountHandle accountHandle = account.getAccountHandle();
+        final UserHandle user = accountHandle.getUserHandle();
+        final ComponentName componentName = accountHandle.getComponentName();
+
+        if (getPhoneAccountHandles(0, null, componentName.getPackageName(),
+                true /* includeDisabled */, user, false /* crossUserAccess */).size()
                 >= MAX_PHONE_ACCOUNT_REGISTRATIONS) {
-            Log.w(this, "Phone account %s reached max registration limit for package",
-                    account.getAccountHandle());
+            EventLog.writeEvent(0x534e4554, "259064622", Binder.getCallingUid(),
+                    "enforceMaxPhoneAccountLimit");
             throw new IllegalArgumentException(
                     "Error, cannot register phone account " + account.getAccountHandle()
                             + " because the limit, " + MAX_PHONE_ACCOUNT_REGISTRATIONS
                             + ", has been reached");
         }
+    }
 
-        addOrReplacePhoneAccount(account);
+    /**
+     * determine if there will be an issue writing the icon to memory
+     *
+     * @param account to enforce check on
+     * @throws IllegalArgumentException if writing the Icon to memory will cause an Exception
+     */
+    @VisibleForTesting
+    public void enforceIconSizeLimit(PhoneAccount account) {
+        if (account.getIcon() == null) {
+            return;
+        }
+        String text = "";
+        // convert the icon into a Base64 String
+        try {
+            text = XmlSerialization.writeIconToBase64String(account.getIcon());
+        } catch (IOException e) {
+            EventLog.writeEvent(0x534e4554, "259064622", Binder.getCallingUid(),
+                    "enforceIconSizeLimit");
+            throw new IllegalArgumentException(ICON_ERROR_MSG);
+        }
+        // enforce the max bytes check in com.android.modules.utils.FastDataOutput#writeUTF(string)
+        try {
+            final int len = (int) ModifiedUtf8.countBytes(text, false);
+            if (len > 65_535 /* MAX_UNSIGNED_SHORT */) {
+                EventLog.writeEvent(0x534e4554, "259064622", Binder.getCallingUid(),
+                        "enforceIconSizeLimit");
+                throw new IllegalArgumentException(ICON_ERROR_MSG);
+            }
+        } catch (IOException e) {
+            EventLog.writeEvent(0x534e4554, "259064622", Binder.getCallingUid(),
+                    "enforceIconSizeLimit");
+            throw new IllegalArgumentException(ICON_ERROR_MSG);
+        }
+    }
+
+    /**
+     * All {@link PhoneAccount} and{@link PhoneAccountHandle} String and Char-Sequence fields
+     * should be restricted to character limit of MAX_PHONE_ACCOUNT_CHAR_LIMIT to prevent exceptions
+     * when writing large character streams to XML-Serializer.
+     *
+     * @param account to enforce character limit checks on
+     * @throws IllegalArgumentException if MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT reached
+     */
+    public void enforceCharacterLimit(PhoneAccount account) {
+        if (account == null) {
+            return;
+        }
+        PhoneAccountHandle handle = account.getAccountHandle();
+
+        String[] fields =
+                {"Package Name", "Class Name", "PhoneAccountHandle Id", "Label", "ShortDescription",
+                        "GroupId", "Address", "SubscriptionAddress"};
+        CharSequence[] args = {handle.getComponentName().getPackageName(),
+                handle.getComponentName().getClassName(), handle.getId(), account.getLabel(),
+                account.getShortDescription(), account.getGroupId(),
+                (account.getAddress() != null ? account.getAddress().toString() : ""),
+                (account.getSubscriptionAddress() != null ?
+                        account.getSubscriptionAddress().toString() : "")};
+
+        for (int i = 0; i < fields.length; i++) {
+            if (args[i] != null && args[i].length() > MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT) {
+                EventLog.writeEvent(0x534e4554, "259064622", Binder.getCallingUid(),
+                        "enforceCharacterLimit");
+                throw new IllegalArgumentException("The PhoneAccount or PhoneAccountHandle ["
+                        + fields[i] + "] field has an invalid character count. PhoneAccount and "
+                        + "PhoneAccountHandle String and Char-Sequence fields are limited to "
+                        + MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT + " characters.");
+            }
+        }
+
+        // Enforce limits on the URI Schemes provided
+        enforceLimitsOnSchemes(account);
+
+        // Enforce limit on the PhoneAccount#mExtras
+        Bundle extras = account.getExtras();
+        if (extras != null) {
+            if (extras.keySet().size() > MAX_PHONE_ACCOUNT_EXTRAS_KEY_PAIR_LIMIT) {
+                EventLog.writeEvent(0x534e4554, "259064622", Binder.getCallingUid(),
+                        "enforceCharacterLimit");
+                throw new IllegalArgumentException("The PhoneAccount#mExtras is limited to " +
+                        MAX_PHONE_ACCOUNT_EXTRAS_KEY_PAIR_LIMIT + " (key,value) pairs.");
+            }
+
+            for (String key : extras.keySet()) {
+                Object value = extras.get(key);
+
+                if ((key != null && key.length() > MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT) ||
+                        (value instanceof String &&
+                                ((String) value).length() > MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT)) {
+                    EventLog.writeEvent(0x534e4554, "259064622", Binder.getCallingUid(),
+                            "enforceCharacterLimit");
+                    throw new IllegalArgumentException("The PhoneAccount#mExtras contains a String"
+                            + " key or value that has an invalid character count. PhoneAccount and "
+                            + "PhoneAccountHandle String and Char-Sequence fields are limited to "
+                            + MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT + " characters.");
+                }
+            }
+        }
+    }
+
+    /**
+     * Enforce a character limit on all PA and PAH string or char-sequence fields.
+     *
+     * @param account to enforce check on
+     * @throws IllegalArgumentException if MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT reached
+     */
+    @VisibleForTesting
+    public void enforceLimitsOnSchemes(@NonNull PhoneAccount account) {
+        List<String> schemes = account.getSupportedUriSchemes();
+
+        if (schemes == null) {
+            return;
+        }
+
+        if (schemes.size() > MAX_SCHEMES_PER_ACCOUNT) {
+            EventLog.writeEvent(0x534e4554, "259064622", Binder.getCallingUid(),
+                    "enforceLimitsOnSchemes");
+            throw new IllegalArgumentException(
+                    "Error, cannot register phone account " + account.getAccountHandle()
+                            + " because the URI scheme limit of "
+                            + MAX_SCHEMES_PER_ACCOUNT + " has been reached");
+        }
+
+        for (String scheme : schemes) {
+            if (scheme.length() > MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT) {
+                EventLog.writeEvent(0x534e4554, "259064622", Binder.getCallingUid(),
+                        "enforceLimitsOnSchemes");
+                throw new IllegalArgumentException(
+                        "Error, cannot register phone account " + account.getAccountHandle()
+                                + " because the max scheme limit of "
+                                + MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT + " has been reached");
+            }
+        }
     }
 
     /**
@@ -904,6 +1063,15 @@
         boolean isEnabled = false;
         boolean isNewAccount;
 
+        // add self-managed capability for transactional accounts that are missing it
+        if (hasTransactionalCallCapabilities(account) &&
+                !account.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)) {
+            account = account.toBuilder()
+                    .setCapabilities(account.getCapabilities()
+                            | PhoneAccount.CAPABILITY_SELF_MANAGED)
+                    .build();
+        }
+
         PhoneAccount oldAccount = getPhoneAccountUnchecked(account.getAccountHandle());
         if (oldAccount != null) {
             enforceSelfManagedAccountUnmodified(account, oldAccount);
@@ -1184,7 +1352,11 @@
             // This may be null if there are no active SIMs but the device is still camped for
             // emergency calls and registered a SIM_SUBSCRIPTION for that purpose.
             TelephonyManager simTm = mTelephonyManager.createForPhoneAccountHandle(simHandle);
-            if (simTm == null) continue;
+            if (simTm == null) {
+                Log.i(this, "maybeNotifyTelephonyForVoiceServiceState: "
+                        + "simTm is null.");
+                continue;
+            }
             simTm.setVoiceServiceStateOverride(hasService);
         }
     }
@@ -1202,6 +1374,7 @@
             Log.w(this, "phoneAccount %s not found", phoneAccountHandle.getComponentName());
             return false;
         }
+
         for (ResolveInfo resolveInfo : resolveInfos) {
             ServiceInfo serviceInfo = resolveInfo.serviceInfo;
             if (serviceInfo == null) {
@@ -1220,6 +1393,15 @@
         return true;
     }
 
+    @VisibleForTesting
+    public boolean hasTransactionalCallCapabilities(PhoneAccount phoneAccount) {
+        if (phoneAccount == null) {
+            return false;
+        }
+        return phoneAccount.hasCapabilities(
+                PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS);
+    }
+
     //
     // Methods for retrieving PhoneAccounts and PhoneAccountHandles
     //
@@ -1266,9 +1448,10 @@
             String uriScheme,
             String packageName,
             boolean includeDisabledAccounts,
-            UserHandle userHandle) {
+            UserHandle userHandle,
+            boolean crossUserAccess) {
         return getPhoneAccountHandles(capabilities, 0 /*excludedCapabilities*/, uriScheme,
-                packageName, includeDisabledAccounts, userHandle);
+                packageName, includeDisabledAccounts, userHandle, crossUserAccess);
     }
 
     /**
@@ -1281,12 +1464,13 @@
             String uriScheme,
             String packageName,
             boolean includeDisabledAccounts,
-            UserHandle userHandle) {
+            UserHandle userHandle,
+            boolean crossUserAccess) {
         List<PhoneAccountHandle> handles = new ArrayList<>();
 
         for (PhoneAccount account : getPhoneAccounts(
                 capabilities, excludedCapabilities, uriScheme, packageName,
-                includeDisabledAccounts, userHandle)) {
+                includeDisabledAccounts, userHandle, crossUserAccess)) {
             handles.add(account.getAccountHandle());
         }
         return handles;
@@ -1297,9 +1481,10 @@
             String uriScheme,
             String packageName,
             boolean includeDisabledAccounts,
-            UserHandle userHandle) {
+            UserHandle userHandle,
+            boolean crossUserAccess) {
         return getPhoneAccounts(capabilities, 0 /*excludedCapabilities*/, uriScheme, packageName,
-                includeDisabledAccounts, userHandle);
+                includeDisabledAccounts, userHandle, crossUserAccess);
     }
 
     /**
@@ -1319,7 +1504,8 @@
             String uriScheme,
             String packageName,
             boolean includeDisabledAccounts,
-            UserHandle userHandle) {
+            UserHandle userHandle,
+            boolean crossUserAccess) {
         List<PhoneAccount> accounts = new ArrayList<>(mState.accounts.size());
         for (PhoneAccount m : mState.accounts) {
             if (!(m.isEnabled() || includeDisabledAccounts)) {
@@ -1342,7 +1528,10 @@
             }
             PhoneAccountHandle handle = m.getAccountHandle();
 
-            if (resolveComponent(handle).isEmpty()) {
+            // PhoneAccounts with CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS do not require a
+            // ConnectionService and will fail [resolveComponent(PhoneAccountHandle)]. Bypass
+            // the [resolveComponent(PhoneAccountHandle)] for transactional accounts.
+            if (!hasTransactionalCallCapabilities(m) && resolveComponent(handle).isEmpty()) {
                 // This component cannot be resolved anymore; skip this one.
                 continue;
             }
@@ -1351,7 +1540,7 @@
                 // Not the right package name; skip this one.
                 continue;
             }
-            if (!isVisibleForUser(m, userHandle, false)) {
+            if (!crossUserAccess && !isVisibleForUser(m, userHandle, false)) {
                 // Account is not visible for the current user; skip this one.
                 continue;
             }
@@ -1778,17 +1967,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 8507703..1d42db4 100644
--- a/src/com/android/server/telecom/RespondViaSmsManager.java
+++ b/src/com/android/server/telecom/RespondViaSmsManager.java
@@ -215,8 +215,9 @@
             MessageSentReceiver receiver = new MessageSentReceiver(
                     !TextUtils.isEmpty(contactName) ? contactName : phoneNumber,
                     messageParts.size());
-            context.registerReceiver(receiver, new IntentFilter(ACTION_MESSAGE_SENT),
-                    Context.RECEIVER_NOT_EXPORTED);
+            IntentFilter messageSentFilter = new IntentFilter(ACTION_MESSAGE_SENT);
+            messageSentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+            context.registerReceiver(receiver, messageSentFilter, 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 c859fde..1710604 100644
--- a/src/com/android/server/telecom/Ringer.java
+++ b/src/com/android/server/telecom/Ringer.java
@@ -21,6 +21,7 @@
 import static android.provider.CallLog.Calls.USER_MISSED_NO_VIBRATE;
 import static android.provider.Settings.Global.ZEN_MODE_OFF;
 
+import android.annotation.NonNull;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.Person;
@@ -32,11 +33,14 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.UserHandle;
+import android.os.UserManager;
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.telecom.Log;
 import android.telecom.TelecomManager;
+import android.view.accessibility.AccessibilityManager;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.telecom.LogUtils.EventTimer;
@@ -47,12 +51,24 @@
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
+import java.util.function.BiConsumer;
+import java.util.function.Supplier;
 
 /**
  * Controls the ringtone player.
  */
 @VisibleForTesting
 public class Ringer {
+    public interface AccessibilityManagerAdapter {
+        boolean startFlashNotificationSequence(@NonNull Context context,
+                @AccessibilityManager.FlashNotificationReason int reason);
+        boolean stopFlashNotificationSequence(@NonNull Context context);
+    }
+    /**
+     * Flag only for local debugging. Do not submit enabled.
+     */
+    private static final boolean DEBUG_RINGER = false;
+
     public static class VibrationEffectProxy {
         public VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
             return VibrationEffect.createWaveform(timings, amplitudes, repeat);
@@ -152,15 +168,15 @@
      */
     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
-     * purposes.
+     * purposes (except mVibratingCall is also used to ensure consistency).
      */
     private Call mRingingCall;
     private Call mVibratingCall;
@@ -189,7 +205,9 @@
             RingtoneFactory ringtoneFactory,
             Vibrator vibrator,
             VibrationEffectProxy vibrationEffectProxy,
-            InCallController inCallController) {
+            InCallController inCallController,
+            NotificationManager notificationManager,
+            AccessibilityManagerAdapter accessibilityManagerAdapter) {
 
         mLock = new Object();
         mSystemSettingsUtil = systemSettingsUtil;
@@ -202,6 +220,8 @@
         mRingtoneFactory = ringtoneFactory;
         mInCallController = inCallController;
         mVibrationEffectProxy = vibrationEffectProxy;
+        mNotificationManager = notificationManager;
+        mAccessibilityManagerAdapter = accessibilityManagerAdapter;
 
         if (mContext.getResources().getBoolean(R.bool.use_simple_vibration_pattern)) {
             mDefaultVibrationEffect = mVibrationEffectProxy.createWaveform(SIMPLE_VIBRATION_PATTERN,
@@ -213,6 +233,8 @@
 
         mIsHapticPlaybackSupportedByDevice =
                 mSystemSettingsUtil.isHapticPlaybackSupported(mContext);
+
+        mAudioManager = mContext.getSystemService(AudioManager.class);
     }
 
     @VisibleForTesting
@@ -220,194 +242,304 @@
         mBlockOnRingingFuture = future;
     }
 
+    @VisibleForTesting
+    public void setNotificationManager(NotificationManager notificationManager) {
+        mNotificationManager = notificationManager;
+    }
+
     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;
+        boolean deferBlockOnRingingFuture = false;
+        // try-finally to ensure that the block on ringing future is always called.
         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 (foregroundCall == null) {
+                Log.wtf(this, "startRinging called with null foreground call.");
+                return false;
             }
-            if (attributes.isSilentRingingRequested()) {
-                Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Silent ringing "
-                        + "requested");
-            }
-            if (mBlockOnRingingFuture != null) {
-                mBlockOnRingingFuture.complete(null);
-            }
-            return attributes.shouldAcquireAudioFocus();
-        }
 
-        stopCallWaiting();
+            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;
+            }
 
-        VibrationEffect effect;
-        CompletableFuture<Boolean> hapticsFuture = null;
-        // Determine if the settings and DND mode indicate that the vibrator can be used right now.
-        boolean isVibratorEnabled = isVibratorEnabled(mContext, attributes.shouldRingForContact());
-        boolean shouldApplyRampingRinger =
-                isVibratorEnabled && mSystemSettingsUtil.isRampingRingerEnabled(mContext);
-        if (attributes.isRingerAudible()) {
-            mRingingCall = foregroundCall;
-            Log.addEvent(foregroundCall, LogUtils.Events.START_RINGER);
-            // Because we wait until a contact info query to complete before processing a
-            // call (for the purposes of direct-to-voicemail), the information about custom
-            // ringtones should be available by the time this code executes. We can safely
-            // request the custom ringtone from the call and expect it to be current.
-            if (shouldApplyRampingRinger) {
-                Log.i(this, "start ramping ringer.");
-                if (mSystemSettingsUtil.isAudioCoupledVibrationForRampingRingerEnabled()) {
-                    effect = getVibrationEffectForCall(mRingtoneFactory, foregroundCall);
-                } else {
-                    effect = mDefaultVibrationEffect;
+            // 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 (mVolumeShaperConfig == null) {
+
+                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();
-                }
-                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);
+                    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 {
-                    Log.addEvent(foregroundCall, LogUtils.Events.SKIP_VIBRATION,
-                            "using audio-coupled haptics");
+                    if (DEBUG_RINGER) {
+                        Log.i(this, "Create ringer with custom vibration effect");
+                    }
+                    // Ramping ringtone is not enabled.
+                    useCustomVibrationEffect = true;
                 }
-            });
-            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();
-    }
-
-    private void maybeStartVibration(Call foregroundCall, boolean shouldRingForContact,
-        VibrationEffect effect, boolean isVibrationEnabled, boolean isRingerAudible) {
-        synchronized (mLock) {
-            mAudioManager = mContext.getSystemService(AudioManager.class);
-            if (isVibrationEnabled && !mIsVibrating && shouldRingForContact) {
-                Log.addEvent(foregroundCall, LogUtils.Events.START_VIBRATOR,
-                        "hasVibrator=%b, userRequestsVibrate=%b, ringerMode=%d, isVibrating=%b",
-                        mVibrator.hasVibrator(),
-                        mSystemSettingsUtil.isRingVibrationEnabled(mContext),
-                        mAudioManager.getRingerMode(), mIsVibrating);
-                if (mSystemSettingsUtil.isRampingRingerEnabled(mContext) && isRingerAudible) {
-                    Log.i(this, "start vibration for ramping ringer.");
-                } else {
-                    Log.i(this, "start normal vibration.");
-                }
-                mIsVibrating = true;
-                mVibrator.vibrate(effect, VIBRATION_ATTRIBUTES);
             } 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);
+            }
+
+            // If vibration will be done, reserve the vibrator.
+            boolean vibratorReserved = isVibratorEnabled && attributes.shouldRingForContact()
+                && tryReserveVibration(foregroundCall);
+            if (!vibratorReserved) {
                 foregroundCall.setUserMissed(USER_MISSED_NO_VIBRATE);
                 Log.addEvent(foregroundCall, LogUtils.Events.SKIP_VIBRATION,
-                        "hasVibrator=%b, userRequestsVibrate=%b, ringerMode=%d, isVibrating=%b",
+                        "hasVibrator=%b, userRequestsVibrate=%b, ringerMode=%d, "
+                                + "isVibratorEnabled=%b",
                         mVibrator.hasVibrator(),
                         mSystemSettingsUtil.isRingVibrationEnabled(mContext),
-                        mAudioManager.getRingerMode(), mIsVibrating);
+                        mAudioManager.getRingerMode(), isVibratorEnabled);
+            }
+
+            // 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;
+            BiConsumer<Ringtone, Boolean> afterRingtoneLogic =
+                    (Ringtone ringtone, Boolean stopped) -> {
+                try {
+                    if (stopped.booleanValue() || !vibratorReserved) {
+                        // don't start vibration if the ringing is already abandoned, or the
+                        // vibrator wasn't reserved. This still triggers the mBlockOnRingingFuture.
+                        return;
+                    }
+                    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, foregroundCall, vibrationEffect);
+                } 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, afterRingtoneLogic);
+            } else {
+                afterRingtoneLogic.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 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;
+    /**
+     * Try to reserve the vibrator for this call, returning false if it's already committed.
+     * The vibration will be started by AsyncRingtonePlayer to ensure timing is aligned with the
+     * audio. The logic uses mVibratingCall to say which call is currently getting ready to vibrate,
+     * or actually vibrating (indicated by mIsVibrating).
+     *
+     * Once reserved, the vibrateIfNeeded method is expected to be called. Note that if
+     * audio-coupled haptics were used instead of vibrator, the reservation still stays until
+     * ringing is stopped, because the vibrator is exclusive to a single vibration source.
+     *
+     * Note that this "reservation" is only local to the Ringer - it's not locking the vibrator, so
+     * if it's busy with some other important vibration, this ringer's one may not displace it.
+     */
+    private boolean tryReserveVibration(Call foregroundCall) {
+        synchronized (mLock) {
+            if (mVibratingCall != null || mIsVibrating) {
+                return false;
             }
+            mVibratingCall = foregroundCall;
+            return true;
+        }
+   }
+
+    private void vibrateIfNeeded(boolean isUsingAudioCoupledHaptics, Call foregroundCall,
+            VibrationEffect effect) {
+        if (isUsingAudioCoupledHaptics) {
+            Log.addEvent(
+                foregroundCall, LogUtils.Events.SKIP_VIBRATION, "using audio-coupled haptics");
+            return;
         }
 
-        if (effect == null) {
-            effect = mDefaultVibrationEffect;
+        synchronized (mLock) {
+            // Ensure the reservation is live. The mIsVibrating check should be redundant.
+            if (foregroundCall == mVibratingCall && !mIsVibrating) {
+                Log.addEvent(foregroundCall, LogUtils.Events.START_VIBRATOR,
+                    "hasVibrator=%b, userRequestsVibrate=%b, ringerMode=%d, isVibrating=%b",
+                    mVibrator.hasVibrator(), mSystemSettingsUtil.isRingVibrationEnabled(mContext),
+                    mAudioManager.getRingerMode(), mIsVibrating);
+                mIsVibrating = true;
+                mVibrator.vibrate(effect, VIBRATION_ATTRIBUTES);
+                Log.i(this, "start vibration.");
+            }
+            // else stopped already: this isn't started unless a reservation was made.
         }
-        return effect;
+    }
+
+    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;
+        }
     }
 
     public void startCallWaiting(Call call) {
@@ -419,7 +551,8 @@
             return;
         }
 
-        if (mInCallController.doesConnectedDialerSupportRinging()) {
+        if (mInCallController.doesConnectedDialerSupportRinging(
+                call.getAssociatedUser())) {
             Log.addEvent(call, LogUtils.Events.SKIP_RINGING, "Dialer handles");
             return;
         }
@@ -443,6 +576,13 @@
     }
 
     public void stopRinging() {
+        final Call foregroundCall = mRingingCall != null ? mRingingCall : mVibratingCall;
+        if (mAccessibilityManagerAdapter != null) {
+            Log.addEvent(foregroundCall, LogUtils.Events.FLASH_NOTIFICATION_STOP);
+            getHandler().post(() ->
+                    mAccessibilityManagerAdapter.stopFlashNotificationSequence(mContext));
+        }
+
         synchronized (mLock) {
             if (mRingingCall != null) {
                 Log.addEvent(mRingingCall, LogUtils.Events.STOP_RINGER);
@@ -451,18 +591,12 @@
 
             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();
                 mIsVibrating = false;
-                mVibratingCall = null;
             }
+            mVibratingCall = null;  // Prevents vibrations from starting via AsyncRingtonePlayer.
         }
     }
 
@@ -483,16 +617,26 @@
         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.i(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);
+        return mNotificationManager.matchesCallFilter(peopleExtras);
     }
 
     private boolean hasExternalRinger(Call foregroundCall) {
@@ -508,10 +652,8 @@
         AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
         // Use AudioManager#getRingerMode for more accurate result, instead of
         // AudioManager#getRingerModeInternal which only useful for volume controllers
-        NotificationManager notificationManager = context.getSystemService(
-                NotificationManager.class);
-        boolean zenModeOn = notificationManager != null
-                && notificationManager.getZenMode() != ZEN_MODE_OFF;
+        boolean zenModeOn = mNotificationManager != null
+                && mNotificationManager.getZenMode() != ZEN_MODE_OFF;
         return mVibrator.hasVibrator()
                 && mSystemSettingsUtil.isRingVibrationEnabled(context)
                 && (audioManager.getRingerMode() != AudioManager.RINGER_MODE_SILENT
@@ -526,22 +668,19 @@
 
         boolean isVolumeOverZero = mAudioManager.getStreamVolume(AudioManager.STREAM_RING) > 0;
         timer.record("isVolumeOverZero");
-        boolean shouldRingForContact = shouldRingForContact(call.getHandle());
+        boolean shouldRingForContact = shouldRingForContact(call);
         timer.record("shouldRingForContact");
-        boolean isRingtonePresent = !(mRingtoneFactory.getRingtone(call) == null);
-        timer.record("getRingtone");
         boolean isSelfManaged = call.isSelfManaged();
         timer.record("isSelfManaged");
         boolean isSilentRingingRequested = call.isSilentRingingRequested();
         timer.record("isSilentRingRequested");
 
-        boolean isRingerAudible = isVolumeOverZero && shouldRingForContact && isRingtonePresent;
+        boolean isRingerAudible = isVolumeOverZero && shouldRingForContact;
         timer.record("isRingerAudible");
         String inaudibleReason = "";
         if (!isRingerAudible) {
-            inaudibleReason = String.format(
-                    "isVolumeOverZero=%s, shouldRingForContact=%s, isRingtonePresent=%s",
-                    isVolumeOverZero, shouldRingForContact, isRingtonePresent);
+            inaudibleReason = String.format("isVolumeOverZero=%s, shouldRingForContact=%s",
+                isVolumeOverZero, shouldRingForContact);
         }
 
         boolean hasExternalRinger = hasExternalRinger(call);
@@ -549,27 +688,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.getAssociatedUser());
         timer.record("letDialerHandleRinging");
+        boolean isWorkProfileInQuietMode =
+                isProfileInQuietMode(call.getAssociatedUser());
+        timer.record("isWorkProfileInQuietMode");
 
         Log.i(this, "startRinging timings: " + timer);
         boolean endEarly = isTheaterModeOn || letDialerHandleRinging || isSelfManaged ||
-                hasExternalRinger || isSilentRingingRequested;
+                hasExternalRinger || isSilentRingingRequested || isWorkProfileInQuietMode;
 
         if (endEarly) {
             Log.i(this, "Ending early -- isTheaterModeOn=%s, letDialerHandleRinging=%s, " +
-                            "isSelfManaged=%s, hasExternalRinger=%s, silentRingingRequested=%s",
+                            "isSelfManaged=%s, hasExternalRinger=%s, silentRingingRequested=%s, " +
+                            "isWorkProfileInQuietMode=%s",
                     isTheaterModeOn, letDialerHandleRinging, isSelfManaged, hasExternalRinger,
-                    isSilentRingingRequested);
+                    isSilentRingingRequested, isWorkProfileInQuietMode);
         }
 
         // Acquire audio focus under any of the following conditions:
         // 1. Should ring for contact and there's an HFP device attached
         // 2. Volume is over zero, we should ring for the contact, and there's a audible ringtone
-        //    present.
+        //    present. (This check is deferred until ringer knows the ringtone)
         // 3. The call is self-managed.
-        boolean shouldAcquireAudioFocus =
-                isRingerAudible || (isHfpDeviceAttached && shouldRingForContact) || isSelfManaged;
+        boolean shouldAcquireAudioFocus = !isWorkProfileInQuietMode &&
+                ((isHfpDeviceAttached && shouldRingForContact) || isSelfManaged);
 
         // Set missed reason according to attributes
         if (!isVolumeOverZero) {
@@ -587,9 +731,15 @@
                 .setInaudibleReason(inaudibleReason)
                 .setShouldRingForContact(shouldRingForContact)
                 .setSilentRingingRequested(isSilentRingingRequested)
+                .setWorkProfileQuietMode(isWorkProfileInQuietMode)
                 .build();
     }
 
+    private boolean isProfileInQuietMode(UserHandle user) {
+        UserManager um = mContext.getSystemService(UserManager.class);
+        return um.isManagedProfile(user.getIdentifier()) && um.isQuietModeEnabled(user);
+    }
+
     private Handler getHandler() {
         if (mHandler == null) {
             HandlerThread handlerThread = new HandlerThread("Ringer");
diff --git a/src/com/android/server/telecom/RingerAttributes.java b/src/com/android/server/telecom/RingerAttributes.java
index 840d815..e0d3e1c 100644
--- a/src/com/android/server/telecom/RingerAttributes.java
+++ b/src/com/android/server/telecom/RingerAttributes.java
@@ -25,6 +25,7 @@
         private String mInaudibleReason;
         private boolean mShouldRingForContact;
         private boolean mSilentRingingRequested;
+        private boolean mWorkProfileQuietMode;
 
         public RingerAttributes.Builder setEndEarly(boolean endEarly) {
             mEndEarly = endEarly;
@@ -61,10 +62,15 @@
             return this;
         }
 
+        public RingerAttributes.Builder setWorkProfileQuietMode(boolean workProfileQuietMode) {
+            mWorkProfileQuietMode = workProfileQuietMode;
+            return this;
+        }
+
         public RingerAttributes build() {
             return new RingerAttributes(mEndEarly, mLetDialerHandleRinging, mAcquireAudioFocus,
                     mRingerAudible, mInaudibleReason, mShouldRingForContact,
-                    mSilentRingingRequested);
+                    mSilentRingingRequested, mWorkProfileQuietMode);
         }
     }
 
@@ -75,10 +81,12 @@
     private String mInaudibleReason;
     private boolean mShouldRingForContact;
     private boolean mSilentRingingRequested;
+    private boolean mWorkProfileQuietMode;
 
     private RingerAttributes(boolean endEarly, boolean letDialerHandleRinging,
             boolean acquireAudioFocus, boolean ringerAudible, String inaudibleReason,
-            boolean shouldRingForContact, boolean silentRingingRequested) {
+            boolean shouldRingForContact, boolean silentRingingRequested,
+            boolean workProfileQuietMode) {
         mEndEarly = endEarly;
         mLetDialerHandleRinging = letDialerHandleRinging;
         mAcquireAudioFocus = acquireAudioFocus;
@@ -86,6 +94,7 @@
         mInaudibleReason = inaudibleReason;
         mShouldRingForContact = shouldRingForContact;
         mSilentRingingRequested = silentRingingRequested;
+        mWorkProfileQuietMode = workProfileQuietMode;
     }
 
     public boolean isEndEarly() {
@@ -115,4 +124,8 @@
     public boolean isSilentRingingRequested() {
         return mSilentRingingRequested;
     }
+
+    public boolean isWorkProfileInQuietMode() {
+        return mWorkProfileQuietMode;
+    }
 }
diff --git a/src/com/android/server/telecom/RingtoneFactory.java b/src/com/android/server/telecom/RingtoneFactory.java
index 5c46998..6bcfb4c 100644
--- a/src/com/android/server/telecom/RingtoneFactory.java
+++ b/src/com/android/server/telecom/RingtoneFactory.java
@@ -65,26 +65,30 @@
     }
 
     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.
+        // or the default ringtone of the receiving user.
         Context userContext = isWorkContact(incomingCall) ?
                 getWorkProfileContextForUser(mCallsManager.getCurrentUserHandle()) :
-                getContextForUserHandle(mCallsManager.getCurrentUserHandle());
+                getContextForUserHandle(incomingCall.getAssociatedUser());
         Uri ringtoneUri = incomingCall.getRingtone();
         Ringtone ringtone = null;
 
-        AudioAttributes audioAttrs = getRingtoneAudioAttributes();
-
-        if(ringtoneUri != null && userContext != null) {
+        if (ringtoneUri != null && userContext != null) {
             // Ringtone URI is explicitly specified. First, try to create a Ringtone with that.
             try {
-              ringtone = RingtoneManager.getRingtone(
-                  userContext, ringtoneUri, volumeShaperConfig, audioAttrs);
-            } catch (NullPointerException npe) {
-                Log.e(this, npe, "getRingtone: NPE while getting ringtone.");
+                ringtone = RingtoneManager.getRingtone(
+                        userContext, ringtoneUri, volumeShaperConfig, audioAttrs);
+            } catch (Exception e) {
+                Log.e(this, e, "getRingtone: exception while getting ringtone.");
             }
         }
-        if(ringtone == null) {
+        if (ringtone == null) {
             // Contact didn't specify ringtone or custom Ringtone creation failed. Get default
             // ringtone for user or profile.
             Context contextToUse = hasDefaultRingtoneForUser(userContext) ? userContext : mContext;
@@ -101,37 +105,40 @@
                     Log.i(this, "getRingtone: Settings.System.DEFAULT_RINGTONE_URI is null.");
                 }
             }
+
             if (defaultRingtoneUri == null) {
                 return null;
             }
+
             try {
                 ringtone = RingtoneManager.getRingtone(
-                    contextToUse, defaultRingtoneUri, volumeShaperConfig, audioAttrs);
-            } catch (NullPointerException npe) {
-                Log.e(this, npe, "getRingtone: NPE while getting ringtone.");
+                        contextToUse, defaultRingtoneUri, volumeShaperConfig, audioAttrs);
+            } catch (Exception e) {
+                Log.e(this, e, "getRingtone: exception while getting ringtone.");
             }
         }
         return ringtone;
     }
 
-    public AudioAttributes getRingtoneAudioAttributes() {
+    private AudioAttributes getDefaultRingtoneAudioAttributes(boolean hapticChannelsMuted) {
         return new AudioAttributes.Builder()
             .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
             .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+            .setHapticChannelsMuted(hapticChannelsMuted)
             .build();
     }
 
-    public Ringtone getRingtone(Call incomingCall) {
-        return getRingtone(incomingCall, null);
-    }
-
     /** Returns a ringtone to be used when ringer is not audible for the incoming call. */
     @Nullable
     public Ringtone getHapticOnlyRingtone() {
+        // Initializing ringtones on the main thread can deadlock
+        ThreadUtil.checkNotOnMainThread();
         Uri ringtoneUri = Uri.parse("file://" + mContext.getString(
                 com.android.internal.R.string.config_defaultRingtoneVibrationSound));
-        AudioAttributes audioAttrs = getRingtoneAudioAttributes();
-        Ringtone ringtone = RingtoneManager.getRingtone(mContext, ringtoneUri, null, audioAttrs);
+        AudioAttributes audioAttrs = getDefaultRingtoneAudioAttributes(
+            /* hapticChannelsMuted */ false);
+        Ringtone ringtone = RingtoneManager.getRingtone(
+            mContext, ringtoneUri, /* volumeShaperConfig */ null, audioAttrs);
         if (ringtone != null) {
             // Make sure the sound is muted.
             ringtone.setVolume(0);
diff --git a/src/com/android/server/telecom/RoleManagerAdapter.java b/src/com/android/server/telecom/RoleManagerAdapter.java
index ba82a06..8fdfb11 100644
--- a/src/com/android/server/telecom/RoleManagerAdapter.java
+++ b/src/com/android/server/telecom/RoleManagerAdapter.java
@@ -41,7 +41,7 @@
      * redirection role.
      * @return the package name of the app filling the role, {@code null} otherwise}.
      */
-    String getDefaultCallRedirectionApp();
+    String getDefaultCallRedirectionApp(UserHandle userHandle);
 
     /**
      * Override the {@link android.app.role.RoleManager} call redirection app with another value.
@@ -56,7 +56,7 @@
      * screening role.
      * @return the package name of the app filling the role, {@code null} otherwise}.
      */
-    String getDefaultCallScreeningApp();
+    String getDefaultCallScreeningApp(UserHandle userHandle);
 
     /**
      * Override the {@link android.app.role.RoleManager} call screening app with another value.
diff --git a/src/com/android/server/telecom/RoleManagerAdapterImpl.java b/src/com/android/server/telecom/RoleManagerAdapterImpl.java
index 4a98d7b..ac35b3d 100644
--- a/src/com/android/server/telecom/RoleManagerAdapterImpl.java
+++ b/src/com/android/server/telecom/RoleManagerAdapterImpl.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.os.Binder;
 import android.os.UserHandle;
 import android.telecom.Log;
 
@@ -50,11 +51,11 @@
     }
 
     @Override
-    public String getDefaultCallRedirectionApp() {
+    public String getDefaultCallRedirectionApp(UserHandle userHandleForCallRedirection) {
         if (mOverrideDefaultCallRedirectionApp != null) {
             return mOverrideDefaultCallRedirectionApp;
         }
-        return getRoleManagerCallRedirectionApp();
+        return getRoleManagerCallRedirectionApp(userHandleForCallRedirection);
     }
 
     @Override
@@ -63,11 +64,11 @@
     }
 
     @Override
-    public String getDefaultCallScreeningApp() {
+    public String getDefaultCallScreeningApp(UserHandle userHandleForCallScreening) {
         if (mOverrideDefaultCallScreeningApp != null) {
             return mOverrideDefaultCallScreeningApp;
         }
-        return getRoleManagerCallScreeningApp();
+        return getRoleManagerCallScreeningApp(userHandleForCallScreening);
     }
 
     @Override
@@ -118,9 +119,9 @@
         mCurrentUserHandle = currentUserHandle;
     }
 
-    private String getRoleManagerCallScreeningApp() {
+    private String getRoleManagerCallScreeningApp(UserHandle userHandle) {
         List<String> roleHolders = mRoleManager.getRoleHoldersAsUser(ROLE_CALL_SCREENING,
-                mCurrentUserHandle);
+                userHandle);
         if (roleHolders == null || roleHolders.isEmpty()) {
             return null;
         }
@@ -141,9 +142,9 @@
         return new ArrayList<>();
     }
 
-    private String getRoleManagerCallRedirectionApp() {
+    private String getRoleManagerCallRedirectionApp(UserHandle userHandle) {
         List<String> roleHolders = mRoleManager.getRoleHoldersAsUser(ROLE_CALL_REDIRECTION_APP,
-                mCurrentUserHandle);
+                userHandle);
         if (roleHolders == null || roleHolders.isEmpty()) {
             return null;
         }
@@ -184,7 +185,7 @@
             pw.print("(override ");
             pw.print(mOverrideDefaultCallRedirectionApp);
             pw.print(") ");
-            pw.print(getRoleManagerCallRedirectionApp());
+            pw.print(getRoleManagerCallRedirectionApp(Binder.getCallingUserHandle()));
         }
         pw.println();
 
@@ -193,7 +194,7 @@
             pw.print("(override ");
             pw.print(mOverrideDefaultCallScreeningApp);
             pw.print(") ");
-            pw.print(getRoleManagerCallScreeningApp());
+            pw.print(getRoleManagerCallScreeningApp(Binder.getCallingUserHandle()));
         }
         pw.println();
 
diff --git a/src/com/android/server/telecom/StatusBarNotifier.java b/src/com/android/server/telecom/StatusBarNotifier.java
index af3493e..772335e 100644
--- a/src/com/android/server/telecom/StatusBarNotifier.java
+++ b/src/com/android/server/telecom/StatusBarNotifier.java
@@ -29,7 +29,7 @@
  */
 @VisibleForTesting
 public class StatusBarNotifier extends CallsManagerListenerBase {
-    private static final String SLOT_MUTE = "mute";
+    private static final String SLOT_MUTE = "telecom_mute";
     private static final String SLOT_SPEAKERPHONE = "speakerphone";
 
     private final Context mContext;
@@ -79,13 +79,15 @@
         mIsShowingMute = isMuted;
     }
 
+    /**
+     * Update the status bar manager with the new speakerphone state.
+     *
+     * IMPORTANT: DO NOT call into any Telecom code here; this is usually scheduled on an async
+     * executor to save Telecom from blocking on outgoing binder calls.
+     * @param isSpeakerphone
+     */
     @VisibleForTesting
     public void notifySpeakerphone(boolean isSpeakerphone) {
-        // Never display anything if there are no calls.
-        if (!mCallsManager.hasAnyCalls()) {
-            isSpeakerphone = false;
-        }
-
         if (mIsShowingSpeakerphone == isSpeakerphone) {
             return;
         }
diff --git a/src/com/android/server/telecom/StreamingCallAdapter.java b/src/com/android/server/telecom/StreamingCallAdapter.java
new file mode 100644
index 0000000..e899aff
--- /dev/null
+++ b/src/com/android/server/telecom/StreamingCallAdapter.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom;
+
+import android.os.Binder;
+import android.os.RemoteException;
+import android.telecom.DisconnectCause;
+import android.telecom.Log;
+import android.telecom.StreamingCall;
+
+import com.android.internal.telecom.IStreamingCallAdapter;
+
+/**
+ * Receives call commands and updates from general call streaming app and passes them through to
+ * the original voip call app. {@link android.telecom.CallStreamingService} creates an instance of
+ * this class and passes it to the general call streaming app after binding to it. This adapter can
+ * receive commands and updates until the general call streaming app is unbound.
+ */
+public class StreamingCallAdapter extends IStreamingCallAdapter.Stub {
+    private final static String TAG = "StreamingCallAdapter";
+
+    private final TransactionalServiceWrapper mTransactionalServiceWrapper;
+    private final Call mCall;
+    private final String mOwnerPackageAbbreviation;
+
+    public StreamingCallAdapter(TransactionalServiceWrapper wrapper, Call call,
+            String ownerPackageName) {
+        mTransactionalServiceWrapper = wrapper;
+        mCall = call;
+        mOwnerPackageAbbreviation = Log.getPackageAbbreviation(ownerPackageName);
+    }
+
+    @Override
+    public void setStreamingState(int state) throws RemoteException {
+        try {
+            Log.startSession(LogUtils.Sessions.CSA_SET_STATE, mOwnerPackageAbbreviation);
+            long token = Binder.clearCallingIdentity();
+            try {
+                Log.i(this, "setStreamingState(%d)", state);
+                switch (state) {
+                    case StreamingCall.STATE_STREAMING:
+                        mTransactionalServiceWrapper.onSetActive(mCall);
+                    case StreamingCall.STATE_HOLDING:
+                        mTransactionalServiceWrapper.onSetInactive(mCall);
+                    case StreamingCall.STATE_DISCONNECTED:
+                        mTransactionalServiceWrapper.onDisconnect(mCall,
+                                new DisconnectCause(DisconnectCause.LOCAL));
+                    default:
+                        // ignore
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        } finally {
+            Log.endSession();
+        }
+    }
+}
diff --git a/src/com/android/server/telecom/SystemStateHelper.java b/src/com/android/server/telecom/SystemStateHelper.java
index dd978c2..5eed1ac 100644
--- a/src/com/android/server/telecom/SystemStateHelper.java
+++ b/src/com/android/server/telecom/SystemStateHelper.java
@@ -146,9 +146,11 @@
         IntentFilter intentFilter1 = new IntentFilter(
                 UiModeManager.ACTION_ENTER_CAR_MODE_PRIORITIZED);
         intentFilter1.addAction(UiModeManager.ACTION_EXIT_CAR_MODE_PRIORITIZED);
+        intentFilter1.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
 
         IntentFilter intentFilter2 = new IntentFilter(Intent.ACTION_PACKAGE_REMOVED);
         intentFilter2.addDataScheme("package");
+        intentFilter2.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
         mContext.registerReceiver(mBroadcastReceiver, intentFilter1);
         mContext.registerReceiver(mBroadcastReceiver, intentFilter2);
         Log.i(this, "Registering broadcast receiver: %s", intentFilter1);
diff --git a/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java b/src/com/android/server/telecom/TelecomBroadcastIntentProcessor.java
index e1f2d08..523b841 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;
@@ -99,6 +101,10 @@
     public static final String ACTION_CANCEL_REDIRECTED_CALL =
             "com.android.server.telecom.CANCEL_REDIRECTED_CALL";
 
+    public static final String ACTION_HANGUP_CALL = "com.android.server.telecom.HANGUP_CALL";
+    public static final String ACTION_STOP_STREAMING =
+            "com.android.server.telecom.ACTION_STOP_STREAMING";
+
     public static final String EXTRA_USERHANDLE = "userhandle";
     public static final String EXTRA_REDIRECTION_OUTGOING_CALL_ID =
             "android.telecom.extra.REDIRECTION_OUTGOING_CALL_ID";
@@ -240,6 +246,26 @@
             } finally {
                 Log.endSession();
             }
+        } else if (ACTION_HANGUP_CALL.equals(action)) {
+            Log.startSession("TBIP.aHC", "streamingDialog");
+            try {
+                Call call = mCallsManager.getCall(intent.getData().getSchemeSpecificPart());
+                if (call != null) {
+                    mCallsManager.disconnectCall(call);
+                }
+            } finally {
+                Log.endSession();
+            }
+        } else if (ACTION_STOP_STREAMING.equals(action)) {
+            Log.startSession("TBIP.aSS", "streamingDialog");
+            try {
+                Call call = mCallsManager.getCall(intent.getData().getSchemeSpecificPart());
+                if (call != null) {
+                    mCallsManager.stopCallStreaming(call);
+                }
+            } finally {
+                Log.endSession();
+            }
         }
     }
 
@@ -247,8 +273,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..f33b185 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
@@ -1567,14 +1811,14 @@
                         Binder.getCallingUid(), callingPackage, callingFeatureId, null)
                         == AppOpsManager.MODE_ALLOWED;
 
-                final boolean hasCallPermission = mContext.checkCallingPermission(CALL_PHONE) ==
-                        PackageManager.PERMISSION_GRANTED;
+                final boolean hasCallPermission = mContext.checkCallingOrSelfPermission(CALL_PHONE)
+                        == PackageManager.PERMISSION_GRANTED;
                 // The Emergency Dialer has call privileged permission and uses this to place
                 // emergency calls.  We ensure permission checks in
                 // NewOutgoingCallIntentBroadcaster#process pass by sending this to
                 // Telecom as an ACTION_CALL_PRIVILEGED intent (which makes sense since the
                 // com.android.phone process has that permission).
-                final boolean hasCallPrivilegedPermission = mContext.checkCallingPermission(
+                final boolean hasCallPrivilegedPermission = mContext.checkCallingOrSelfPermission(
                         CALL_PRIVILEGED) == PackageManager.PERMISSION_GRANTED;
 
                 synchronized (mLock) {
@@ -1584,16 +1828,13 @@
                         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)
+                                                || hasCallPrivilegedPermission,
                                         true /* isLocalInvocation */);
                     } finally {
                         Binder.restoreCallingIdentity(token);
@@ -1633,10 +1874,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 +1922,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 +1941,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 +1979,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 +2012,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 +2037,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 +2150,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 +2166,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.getAssociatedUser());
                         }
-                        mCallsManager.getInCallController().unbindFromServices();
+                        for (UserHandle userHandle : userHandles) {
+                            mCallsManager.getInCallController().unbindFromServices(userHandle);
+                        }
                     });
                 }
             } finally {
@@ -1930,7 +2187,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 +2382,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 +2412,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 +2476,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 +2514,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 +2634,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 +2688,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 +2835,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 +2856,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) {
@@ -2619,6 +3006,11 @@
             return true;
         }
 
+        if (mContext.checkCallingOrSelfPermission(CALL_PRIVILEGED)
+                == PackageManager.PERMISSION_GRANTED) {
+            return true;
+        }
+
         // Accessing phone state is gated by a special permission.
         mContext.enforceCallingOrSelfPermission(CALL_PHONE, message);
 
@@ -2752,4 +3144,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..67bb81f 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -16,43 +16,50 @@
 
 package com.android.server.telecom;
 
-import com.android.internal.annotations.VisibleForTesting;
-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.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.Manifest;
 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.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.CallStreamingNotification;
+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.ui.ToastFactory;
+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.
@@ -109,6 +116,11 @@
                 .addDataAuthority(DialerCodeReceiver.TELECOM_SECRET_CODE_MARK, null);
         DIALER_SECRET_CODE_FILTER
                 .addDataAuthority(DialerCodeReceiver.TELECOM_SECRET_CODE_MENU, null);
+
+        USER_SWITCHED_FILTER.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+        USER_STARTING_FILTER.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+        BOOT_COMPLETE_FILTER.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+        DIALER_SECRET_CODE_FILTER.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
     }
 
     private static TelecomSystem INSTANCE = null;
@@ -208,9 +220,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 +286,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 +345,23 @@
                 }
             };
 
+            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();
+
+            CallStreamingNotification callStreamingNotification =
+                    new CallStreamingNotification(mContext,
+                            packageName -> AppLabelProxy.Util.getAppLabel(
+                                    mContext.getPackageManager(), packageName), asyncTaskExecutor);
+
             mCallsManager = new CallsManager(
                     mContext,
                     mLock,
@@ -348,7 +391,15 @@
                     inCallControllerFactory,
                     callDiagnosticServiceController,
                     roleManagerAdapter,
-                    toastFactory);
+                    toastFactory,
+                    callEndpointControllerFactory,
+                    callAnomalyWatchdog,
+                    accessibilityManagerAdapter,
+                    asyncTaskExecutor,
+                    blockedNumbersAdapter,
+                    transactionManager,
+                    emergencyCallDiagnosticLogger,
+                    callStreamingNotification);
 
             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..15278e1
--- /dev/null
+++ b/src/com/android/server/telecom/TransactionalServiceRepository.java
@@ -0,0 +1,80 @@
+/*
+ * 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.Log;
+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 String TAG = TransactionalServiceRepository.class.getSimpleName();
+    private static final Map<PhoneAccountHandle, TransactionalServiceWrapper> mServiceLookupTable =
+            new HashMap<>();
+
+    public TransactionalServiceRepository() {
+    }
+
+    public TransactionalServiceWrapper addNewCallForTransactionalServiceWrapper
+            (PhoneAccountHandle phoneAccountHandle, ICallEventCallback callEventCallback,
+                    CallsManager callsManager, Call call) {
+        TransactionalServiceWrapper service;
+        // Only create a new TransactionalServiceWrapper if this is the first call for a package.
+        // Otherwise, get the existing TSW and add the new call to the service.
+        if (!hasExistingServiceWrapper(phoneAccountHandle)) {
+            Log.d(TAG, "creating a new TSW; handle=[%s]", phoneAccountHandle);
+            service = new TransactionalServiceWrapper(callEventCallback,
+                    callsManager, phoneAccountHandle, call, this);
+        } else {
+            Log.d(TAG, "add a new call to an existing TSW; handle=[%s]", phoneAccountHandle);
+            service = getTransactionalServiceWrapper(phoneAccountHandle);
+            if (service == null) {
+                throw new IllegalStateException("service is null");
+            } else {
+                service.trackCall(call);
+            }
+        }
+
+        mServiceLookupTable.put(phoneAccountHandle, service);
+
+        return service;
+    }
+
+    public TransactionalServiceWrapper getTransactionalServiceWrapper(PhoneAccountHandle pah) {
+        return mServiceLookupTable.get(pah);
+    }
+
+    public boolean hasExistingServiceWrapper(PhoneAccountHandle pah) {
+        return mServiceLookupTable.containsKey(pah);
+    }
+
+    public boolean removeServiceWrapper(PhoneAccountHandle pah) {
+        Log.i(TAG, "removeServiceWrapper: for phoneAccountHandle=[%s]", pah);
+        if (!hasExistingServiceWrapper(pah)) {
+            return false;
+        }
+        mServiceLookupTable.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..25aaad7
--- /dev/null
+++ b/src/com/android/server/telecom/TransactionalServiceWrapper.java
@@ -0,0 +1,659 @@
+/*
+ * 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.List;
+import java.util.Locale;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * 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 {
+    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 ConcurrentHashMap<String, Call> mTrackedCalls = new ConcurrentHashMap<>();
+    private final TelecomSystem.SyncRoot mLock;
+    private final String mPackageName;
+    // needs to be non-final for testing
+    private TransactionManager mTransactionManager;
+    private CallStreamingController mStreamingController;
+
+
+    // Each TransactionalServiceWrapper should have their own Binder.DeathRecipient to clean up
+    // any calls in the event the application crashes or is force stopped.
+    private final IBinder.DeathRecipient mAppDeathListener = new IBinder.DeathRecipient() {
+        @Override
+        public void binderDied() {
+            Log.i(TAG, "binderDied: for package=[%s]; cleaning calls", mPackageName);
+            cleanupTransactionalServiceWrapper();
+            mICallEventCallback.asBinder().unlinkToDeath(this, 0);
+        }
+    };
+
+    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();
+        setDeathRecipient(callEventCallback);
+    }
+
+    @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);
+            }
+        }
+    }
+
+    @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;
+    }
+
+    public void cleanupTransactionalServiceWrapper() {
+        for (Call call : mTrackedCalls.values()) {
+            mCallsManager.markCallAsDisconnected(call,
+                    new DisconnectCause(DisconnectCause.ERROR, "process died"));
+            mCallsManager.removeCall(call); // This will clear mTrackedCalls && ClientTWS
+        }
+    }
+
+    /***
+     *********************************************************************************************
+     **                        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(mStreamingController.getStartStreamingTransaction(mCallsManager,
+                                TransactionalServiceWrapper.this, call, mLock), 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();
+            }
+        }
+    };
+
+    public 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) {
+                        if (isAnswerRequest) {
+                            // This also sends the signal to untrack from TSW and the client_TSW
+                            removeCallFromCallsManager(call,
+                                    new DisconnectCause(DisconnectCause.REJECTED,
+                                            "client rejected to answer the call;"
+                                                    + " force disconnecting"));
+                        } else {
+                            mCallsManager.markCallAsOnHold(call);
+                        }
+                        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());
+            } catch (RemoteException e) {
+            }
+            // remove the call from this class/wrapper (server side)
+            untrackCall(call);
+        }
+    }
+
+    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);
+    }
+
+    private void setDeathRecipient(ICallEventCallback callEventCallback) {
+        try {
+            callEventCallback.asBinder().linkToDeath(mAppDeathListener, 0);
+        } catch (Exception e) {
+            Log.w(TAG, "setDeathRecipient: hit exception=[%s] trying to link binder to death",
+                    e.toString());
+        }
+    }
+
+    /***
+     *********************************************************************************************
+     **                    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                                                        **
+     *********************************************************************************************
+     */
+
+    public void stopCallStreaming(Call call) {
+        Log.i(this, "stopCallStreaming; callid=%s", call.getId());
+        if (call != null && call.isStreaming()) {
+            VoipCallTransaction stopStreamingTransaction = mStreamingController
+                    .getStopStreamingTransaction(call, mLock);
+            addTransactionsToManager(stopStreamingTransaction, new ResultReceiver(null));
+        }
+    }
+}
diff --git a/src/com/android/server/telecom/TtyManager.java b/src/com/android/server/telecom/TtyManager.java
index 10e3348..53e917d 100644
--- a/src/com/android/server/telecom/TtyManager.java
+++ b/src/com/android/server/telecom/TtyManager.java
@@ -50,6 +50,7 @@
 
         IntentFilter intentFilter = new IntentFilter(
                 TelecomManager.ACTION_TTY_PREFERRED_MODE_CHANGED);
+        intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
         mContext.registerReceiver(mReceiver, intentFilter,
                 android.Manifest.permission.MODIFY_PHONE_STATE,
                 null, Context.RECEIVER_EXPORTED);
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
index 8fbb1fe..09b8f76 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
@@ -48,6 +48,7 @@
         INTENT_FILTER.addAction(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED);
         INTENT_FILTER.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED);
         INTENT_FILTER.addAction(BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED);
+        INTENT_FILTER.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
     }
 
     // If not in a call, BSR won't listen to the Bluetooth stack's HFP on/off messages, since
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..f07c0aa 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.getAssociatedUser(), mPackageName, connection)) {
             Log.i(this, "Call screening service binding failed.");
             resultFuture.complete(mPriorStageResult);
         } else {
diff --git a/src/com/android/server/telecom/callfiltering/DndCallFilter.java b/src/com/android/server/telecom/callfiltering/DndCallFilter.java
new file mode 100644
index 0000000..f6ed646
--- /dev/null
+++ b/src/com/android/server/telecom/callfiltering/DndCallFilter.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom.callfiltering;
+
+import android.telecom.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.LogUtils;
+import com.android.server.telecom.Ringer;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+
+/**
+ * DndCallFilter is a incoming call filter that adds the
+ * {@link android.telecom.Call.EXTRA_IS_SUPPRESSED_BY_DO_NOT_DISTURB } early in the call processing.
+ * Adding {@link android.telecom.Call.EXTRA_IS_SUPPRESSED_BY_DO_NOT_DISTURB } before the Call object
+ * is passed to all InCallServices is crucial for InCallServices that may disrupt the user and
+ * potentially bypass the current Do Not Disturb settings.
+ */
+public class DndCallFilter extends CallFilter {
+
+    private final Call mCall;
+    private final Ringer mRinger;
+
+    public DndCallFilter(Call call, Ringer ringer) {
+        mCall = call;
+        mRinger = ringer;
+    }
+
+    @VisibleForTesting
+    @Override
+    public CompletionStage<CallFilteringResult> startFilterLookup(CallFilteringResult result) {
+        CompletableFuture<CallFilteringResult> resultFuture = new CompletableFuture<>();
+
+        // start timer for query to NotificationManager
+        Log.addEvent(mCall, LogUtils.Events.DND_PRE_CHECK_INITIATED);
+
+        // query NotificationManager to determine if the call should ring or be suppressed
+        boolean shouldSuppress = !mRinger.shouldRingForContact(mCall);
+
+        // end timer
+        Log.addEvent(mCall, LogUtils.Events.DND_PRE_CHECK_COMPLETED, shouldSuppress);
+
+        // complete the resultFuture object
+        resultFuture.complete(new CallFilteringResult.Builder()
+                .setShouldAllowCall(true)
+                .setShouldAddToCallLog(true)
+                .setShouldShowNotification(true)
+                .setDndSuppressed(shouldSuppress)
+                .build());
+
+        return resultFuture;
+    }
+
+}
diff --git a/src/com/android/server/telecom/callfiltering/IncomingCallFilterGraph.java b/src/com/android/server/telecom/callfiltering/IncomingCallFilterGraph.java
index 9fa864e..d79e80e 100644
--- a/src/com/android/server/telecom/callfiltering/IncomingCallFilterGraph.java
+++ b/src/com/android/server/telecom/callfiltering/IncomingCallFilterGraph.java
@@ -41,6 +41,7 @@
                     .setShouldReject(false)
                     .setShouldAddToCallLog(true)
                     .setShouldShowNotification(true)
+                    .setDndSuppressed(false)
                     .build();
 
     private final CallFilterResultCallback mListener;
@@ -143,6 +144,7 @@
                 .setShouldSilence(false)
                 .setShouldAddToCallLog(true)
                 .setShouldShowNotification(true)
+                .setDndSuppressed(false)
                 .build();
         for (CallFilter dependencyFilter : filter.getDependencies()) {
             result = result.combine(dependencyFilter.getResult());
diff --git a/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java b/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java
index 7469650..963e923 100644
--- a/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java
+++ b/src/com/android/server/telecom/callredirection/CallRedirectionProcessor.java
@@ -74,7 +74,7 @@
             mServiceType = serviceType;
         }
 
-        private void process() {
+        private void process(UserHandle userHandleForCallRedirection) {
             Intent intent = new Intent(CallRedirectionService.SERVICE_INTERFACE)
                     .setComponent(mComponentName);
             ServiceConnection connection = new CallRedirectionServiceConnection();
@@ -83,7 +83,7 @@
                     connection,
                     Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
                     | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS,
-                    UserHandle.CURRENT)) {
+                    userHandleForCallRedirection)) {
                 Log.d(this, "bindService, found " + mServiceType + " call redirection service,"
                         + " waiting for it to connect");
                 mConnection = connection;
@@ -340,7 +340,8 @@
                                 mPhoneAccountHandle, mRedirectionGatewayInfo, mSpeakerphoneOn,
                                 mVideoState, mShouldCancelCall, mUiAction);
                     } else {
-                        performCarrierCallRedirection();
+                        // Use the current user for carrier call redirection
+                        performCarrierCallRedirection(UserHandle.CURRENT);
                     }
                 } else if (mIsCarrierRedirectionPending) {
                     Log.addEvent(mCall, LogUtils.Events.REDIRECTION_COMPLETED_CARRIER);
@@ -356,39 +357,41 @@
     /**
      * The entry to perform call redirection of the call from (@link CallsManager)
      */
-    public void performCallRedirection() {
+    public void performCallRedirection(UserHandle userHandleForCallRedirection) {
         // If the Gateway Info is set with intent, only request with carrier call redirection.
         if (mRedirectionGatewayInfo != null) {
-            performCarrierCallRedirection();
+            // Use the current user for carrier call redirection
+            performCarrierCallRedirection(UserHandle.CURRENT);
         } else {
-            performUserDefinedCallRedirection();
+            performUserDefinedCallRedirection(userHandleForCallRedirection);
         }
     }
 
-    private void performUserDefinedCallRedirection() {
+    private void performUserDefinedCallRedirection(UserHandle userHandleForCallRedirection) {
         Log.d(this, "performUserDefinedCallRedirection");
         ComponentName componentName =
-                mCallRedirectionProcessorHelper.getUserDefinedCallRedirectionService();
+                mCallRedirectionProcessorHelper.
+                        getUserDefinedCallRedirectionService(userHandleForCallRedirection);
         if (componentName != null) {
             mAttempt = new CallRedirectionAttempt(componentName, SERVICE_TYPE_USER_DEFINED);
-            mAttempt.process();
+            mAttempt.process(userHandleForCallRedirection);
             mIsUserDefinedRedirectionPending = true;
             processTimeoutForCallRedirection(SERVICE_TYPE_USER_DEFINED);
         } else {
             Log.i(this, "There are no user-defined call redirection services installed on this"
                     + " device.");
-            performCarrierCallRedirection();
+            performCarrierCallRedirection(UserHandle.CURRENT);
         }
     }
 
-    private void performCarrierCallRedirection() {
+    private void performCarrierCallRedirection(UserHandle userHandleForCallRedirection) {
         Log.d(this, "performCarrierCallRedirection");
         ComponentName componentName =
                 mCallRedirectionProcessorHelper.getCarrierCallRedirectionService(
                         mPhoneAccountHandle);
         if (componentName != null) {
             mAttempt = new CallRedirectionAttempt(componentName, SERVICE_TYPE_CARRIER);
-            mAttempt.process();
+            mAttempt.process(userHandleForCallRedirection);
             mIsCarrierRedirectionPending = true;
             processTimeoutForCallRedirection(SERVICE_TYPE_CARRIER);
         } else {
@@ -429,18 +432,22 @@
     }
 
     /**
-     * Checks if Telecom can make call redirection with any available call redirection service.
+     * Checks if Telecom can make call redirection with any available call redirection service
+     * as the specified user.
      *
      * @return {@code true} if it can; {@code false} otherwise.
      */
-    public boolean canMakeCallRedirectionWithService() {
-        boolean canMakeCallRedirectionWithService =
-                mCallRedirectionProcessorHelper.getUserDefinedCallRedirectionService() != null
+    public boolean canMakeCallRedirectionWithServiceAsUser(
+            UserHandle userHandleForCallRedirection) {
+        boolean canMakeCallRedirectionWithServiceAsUser =
+                mCallRedirectionProcessorHelper
+                        .getUserDefinedCallRedirectionService(userHandleForCallRedirection) != null
                         || mCallRedirectionProcessorHelper.getCarrierCallRedirectionService(
                                 mPhoneAccountHandle) != null;
-        Log.i(this, "Can make call redirection with any available service: "
-                + canMakeCallRedirectionWithService);
-        return canMakeCallRedirectionWithService;
+        Log.i(this, "Can make call redirection with any "
+                + "available service as user (" + userHandleForCallRedirection
+                + ") : " + canMakeCallRedirectionWithServiceAsUser);
+        return canMakeCallRedirectionWithServiceAsUser;
     }
 
     /**
diff --git a/src/com/android/server/telecom/callredirection/CallRedirectionProcessorHelper.java b/src/com/android/server/telecom/callredirection/CallRedirectionProcessorHelper.java
index 9771d65..7a2d415 100644
--- a/src/com/android/server/telecom/callredirection/CallRedirectionProcessorHelper.java
+++ b/src/com/android/server/telecom/callredirection/CallRedirectionProcessorHelper.java
@@ -23,6 +23,7 @@
 import android.content.pm.ResolveInfo;
 import android.net.Uri;
 import android.os.PersistableBundle;
+import android.os.UserHandle;
 import android.telecom.CallRedirectionService;
 import android.telecom.GatewayInfo;
 import android.telecom.Log;
@@ -53,8 +54,9 @@
     }
 
     @VisibleForTesting
-    public ComponentName getUserDefinedCallRedirectionService() {
-        String packageName = mCallsManager.getRoleManagerAdapter().getDefaultCallRedirectionApp();
+    public ComponentName getUserDefinedCallRedirectionService(UserHandle userHandle) {
+        String packageName = mCallsManager.getRoleManagerAdapter().getDefaultCallRedirectionApp(
+                userHandle);
         if (TextUtils.isEmpty(packageName)) {
             Log.i(this, "PackageName is empty. Not performing user-defined call redirection.");
             return null;
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index 9ad0da4..ef85fc7 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -26,9 +26,11 @@
 import android.os.PowerManager;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.provider.BlockedNumberContract;
 import android.telecom.Log;
 
 import android.telecom.CallerInfoAsyncQuery;
+import android.view.accessibility.AccessibilityManager;
 
 import com.android.internal.telecom.IInternalServiceRetriever;
 import com.android.internal.telecom.ITelecomLoader;
@@ -53,14 +55,19 @@
 import com.android.server.telecom.ProximitySensorManagerFactory;
 import com.android.server.telecom.InCallWakeLockController;
 import com.android.server.telecom.ProximitySensorManager;
+import com.android.server.telecom.Ringer;
 import com.android.server.telecom.RoleManagerAdapterImpl;
 import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.TelecomWakeLock;
 import com.android.server.telecom.Timeouts;
+import com.android.server.telecom.callfiltering.BlockedNumbersAdapter;
+import com.android.server.telecom.settings.BlockedNumbersUtil;
 import com.android.server.telecom.ui.IncomingCallNotifier;
 import com.android.server.telecom.ui.MissedCallNotifierImpl;
 import com.android.server.telecom.ui.NotificationChannelManager;
 
+import java.util.concurrent.Executors;
+
 /**
  * Implementation of the ITelecom interface.
  */
@@ -154,7 +161,7 @@
                             new InCallWakeLockControllerFactory() {
                                 @Override
                                 public InCallWakeLockController create(Context context,
-                                                                       CallsManager callsManager) {
+                                        CallsManager callsManager) {
                                     return new InCallWakeLockController(
                                             new TelecomWakeLock(context,
                                                     PowerManager.FULL_WAKE_LOCK,
@@ -191,7 +198,38 @@
                             new RoleManagerAdapterImpl(context,
                                     (RoleManager) context.getSystemService(Context.ROLE_SERVICE)),
                             new ContactsAsyncHelper.Factory(),
-                            internalServiceRetriever.getDeviceIdleController()));
+                            internalServiceRetriever.getDeviceIdleController(),
+                            new Ringer.AccessibilityManagerAdapter() {
+                                @Override
+                                public boolean startFlashNotificationSequence(
+                                        @androidx.annotation.NonNull Context context, int reason) {
+                                    return context.getSystemService(AccessibilityManager.class)
+                                            .startFlashNotificationSequence(context, reason);
+                                }
+
+                                @Override
+                                public boolean stopFlashNotificationSequence(
+                                        @androidx.annotation.NonNull Context context) {
+                                    return context.getSystemService(AccessibilityManager.class)
+                                            .stopFlashNotificationSequence(context);
+                                }
+                            },
+                            Executors.newCachedThreadPool(),
+                            new BlockedNumbersAdapter() {
+                                @Override
+                                public boolean shouldShowEmergencyCallNotification(Context
+                                        context) {
+                                    return BlockedNumberContract.SystemContract
+                                            .shouldShowEmergencyCallNotification(context);
+                                }
+
+                                @Override
+                                public void updateEmergencyCallNotification(Context context,
+                                        boolean showNotification) {
+                                    BlockedNumbersUtil.updateEmergencyCallNotification(context,
+                                            showNotification);
+                                }
+                            }));
         }
     }
 
diff --git a/src/com/android/server/telecom/components/UserCallActivity.java b/src/com/android/server/telecom/components/UserCallActivity.java
index 1d85884..d7b2001 100644
--- a/src/com/android/server/telecom/components/UserCallActivity.java
+++ b/src/com/android/server/telecom/components/UserCallActivity.java
@@ -74,7 +74,8 @@
             // ActivityThread.ActivityClientRecord#intent directly.
             // Modifying directly may be a potential risk when relaunching this activity.
             new UserCallIntentProcessor(this, userHandle).processIntent(new Intent(intent),
-                    getCallingPackage(), true /* hasCallAppOp*/, false /* isLocalInvocation */);
+                    getCallingPackage(), false, true /* hasCallAppOp*/,
+                    false /* isLocalInvocation */);
         } finally {
             Log.endSession();
             wakelock.release();
diff --git a/src/com/android/server/telecom/components/UserCallIntentProcessor.java b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
index cad7b4c..a4602c1 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) {
+            // 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..5fa5f06 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)) {
@@ -153,8 +154,11 @@
                 updateButterBar();
             }
         };
-        registerReceiver(mBlockingStatusReceiver, new IntentFilter(
-                BlockedNumberContract.SystemContract.ACTION_BLOCK_SUPPRESSION_STATE_CHANGED));
+        IntentFilter blockStatusIntentFilter = new IntentFilter(
+                BlockedNumberContract.SystemContract.ACTION_BLOCK_SUPPRESSION_STATE_CHANGED);
+        blockStatusIntentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+        registerReceiver(mBlockingStatusReceiver, blockStatusIntentFilter,
+                Context.RECEIVER_EXPORTED);
 
         getLoaderManager().initLoader(0, null, this);
     }
diff --git a/src/com/android/server/telecom/stats/CallFailureCause.java b/src/com/android/server/telecom/stats/CallFailureCause.java
new file mode 100644
index 0000000..3eb2321
--- /dev/null
+++ b/src/com/android/server/telecom/stats/CallFailureCause.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.telecom.stats;
+
+/**
+ * Indicating the failure reason why a new call cannot be made.
+ * The codes are synced with CallFailureCauseEnum defined in enums.proto.
+ */
+public enum CallFailureCause {
+    /** The call is normally started. */
+    NONE(0),
+    /** Necessary parameters are invalid or null. */
+    INVALID_USE(1),
+    /** There is an emergency call ongoing. */
+    IN_EMERGENCY_CALL(2),
+    /** There is an live call that cannot be held. */
+    CANNOT_HOLD_CALL(3),
+    /** There are maximum number of outgoing calls already. */
+    MAX_OUTGOING_CALLS(4),
+    /** There are maximum number of ringing calls already. */
+    MAX_RINGING_CALLS(5),
+    /** There are maximum number of calls in hold already. */
+    MAX_HOLD_CALLS(6),
+    /* There are maximum number of self-managed calls already. */
+    MAX_SELF_MANAGED_CALLS(7);
+
+    private final int mCode;
+
+    /**
+     * Creates a new CallFailureCause.
+     *
+     * @param code The code for the failure cause.
+     */
+    CallFailureCause(int code) {
+        mCode = code;
+    }
+
+    /**
+     * Returns the code for the failure.
+     *
+     * @return The code for the failure cause.
+     */
+    public int getCode() {
+        return mCode;
+    }
+
+    /**
+     * Check if this enum represents a non-failure case.
+     *
+     * @return True if success.
+     */
+    public boolean isSuccess() {
+        return this == NONE;
+    }
+}
diff --git a/src/com/android/server/telecom/stats/CallStateChangedAtomWriter.java b/src/com/android/server/telecom/stats/CallStateChangedAtomWriter.java
new file mode 100644
index 0000000..55b002e
--- /dev/null
+++ b/src/com/android/server/telecom/stats/CallStateChangedAtomWriter.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.telecom.stats;
+
+import android.content.pm.PackageManager;
+import android.telecom.DisconnectCause;
+import android.telecom.Log;
+
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.TelecomStatsLog;
+
+/**
+ * Collects and stores data for CallStateChanged atom for each call, and provide a
+ * method to write the data to statsd whenever the call state changes.
+ */
+public class CallStateChangedAtomWriter {
+    private boolean mIsSelfManaged = false;
+    private boolean mIsExternalCall = false;
+    private boolean mIsEmergencyCall = false;
+    private int mUid = -1;
+    private int mDurationSeconds = 0;
+    private int mExistingCallCount = 0;
+    private int mHeldCallCount = 0;
+    private CallFailureCause mStartFailCause = CallFailureCause.NONE;
+    private DisconnectCause mDisconnectCause = new DisconnectCause(DisconnectCause.UNKNOWN);
+
+    /**
+     * Write collected data and current call state to statsd.
+     *
+     * @param state Current call state.
+     */
+    public void write(int state) {
+        TelecomStatsLog.write(TelecomStatsLog.CALL_STATE_CHANGED,
+                state,
+                state == CallState.DISCONNECTED ?
+                    mDisconnectCause.getCode() : DisconnectCause.UNKNOWN,
+                mIsSelfManaged,
+                mIsExternalCall,
+                mIsEmergencyCall,
+                mUid,
+                state == CallState.DISCONNECTED ? mDurationSeconds : 0,
+                mExistingCallCount,
+                mHeldCallCount,
+                state == CallState.DISCONNECTED ?
+                    mStartFailCause.getCode() : CallFailureCause.NONE.getCode());
+    }
+
+    public CallStateChangedAtomWriter setSelfManaged(boolean isSelfManaged) {
+        mIsSelfManaged = isSelfManaged;
+        return this;
+    }
+
+    public CallStateChangedAtomWriter setExternalCall(boolean isExternalCall) {
+        mIsExternalCall = isExternalCall;
+        return this;
+    }
+
+    public CallStateChangedAtomWriter setEmergencyCall(boolean isEmergencyCall) {
+        mIsEmergencyCall = isEmergencyCall;
+        return this;
+    }
+
+    public CallStateChangedAtomWriter setUid(int uid) {
+        mUid = uid;
+        return this;
+    }
+
+    public CallStateChangedAtomWriter setUid(String packageName, PackageManager pm) {
+        try {
+            final int uid = pm.getPackageUid(packageName, PackageManager.PackageInfoFlags.of(0));
+            return setUid(uid);
+
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(this, e, "Could not find the package");
+        }
+        return setUid(-1);
+    }
+
+    public CallStateChangedAtomWriter setDurationSeconds(int duration) {
+        if (duration >= 0) {
+            mDurationSeconds = duration;
+        }
+        return this;
+    }
+
+    public CallStateChangedAtomWriter setExistingCallCount(int count) {
+        mExistingCallCount = count;
+        return this;
+    }
+
+    public CallStateChangedAtomWriter increaseHeldCallCount() {
+        mHeldCallCount++;
+        return this;
+    }
+
+    public CallStateChangedAtomWriter setDisconnectCause(DisconnectCause cause) {
+        mDisconnectCause = cause;
+        return this;
+    }
+
+    public CallStateChangedAtomWriter setStartFailCause(CallFailureCause cause) {
+        mStartFailCause = cause;
+        return this;
+    }
+}
diff --git a/src/com/android/server/telecom/ui/AudioProcessingNotification.java b/src/com/android/server/telecom/ui/AudioProcessingNotification.java
index 7a61460..952bee8 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.getAssociatedUser());
         }
     }
 
@@ -66,7 +68,8 @@
     @Override
     public void onCallRemoved(Call call) {
         if (call == mCallInAudioProcessing) {
-            cancelAudioProcessingNotification();
+            cancelAudioProcessingNotification(
+                    call.getAssociatedUser());
         }
     }
 
@@ -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.getAssociatedUser());
         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.getAssociatedUser());
     }
 
     /** 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/CallStreamingNotification.java b/src/com/android/server/telecom/ui/CallStreamingNotification.java
new file mode 100644
index 0000000..8414047
--- /dev/null
+++ b/src/com/android/server/telecom/ui/CallStreamingNotification.java
@@ -0,0 +1,324 @@
+/*
+ * 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.ui;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Person;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.net.Uri;
+import android.os.UserHandle;
+import android.telecom.Log;
+import android.telecom.PhoneAccount;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.TextUtils;
+import android.text.style.ForegroundColorSpan;
+import androidx.core.graphics.drawable.RoundedBitmapDrawable;
+import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.server.telecom.AppLabelProxy;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallsManagerListenerBase;
+import com.android.server.telecom.R;
+import com.android.server.telecom.TelecomBroadcastIntentProcessor;
+import com.android.server.telecom.components.TelecomBroadcastReceiver;
+
+import java.util.concurrent.Executor;
+
+/**
+ * Class responsible for tracking if there is a call which is being streamed and posting a
+ * notification which informs the user that a call is streaming.  The user has two possible actions:
+ * disconnect the call, bring the call back to the current device (stop streaming).
+ */
+public class CallStreamingNotification extends CallsManagerListenerBase implements Call.Listener {
+    // URI scheme used for data related to the notification actions.
+    public static final String CALL_ID_SCHEME = "callid";
+    // The default streaming notification ID.
+    private static final int STREAMING_NOTIFICATION_ID = 90210;
+    // Tag for streaming notification.
+    private static final String NOTIFICATION_TAG =
+            CallStreamingNotification.class.getSimpleName();
+
+    private final Context mContext;
+    private final NotificationManager mNotificationManager;
+    // Used to get the app name for the notification.
+    private final AppLabelProxy mAppLabelProxy;
+    // An executor that can be used to fire off async tasks that do not block Telecom in any manner.
+    private final Executor mAsyncTaskExecutor;
+    // The call which is streaming.
+    private Call mStreamingCall;
+    // Lock for notification post/remove -- these happen outside the Telecom sync lock.
+    private final Object mNotificationLock = new Object();
+
+    // Whether the notification is showing.
+    @GuardedBy("mNotificationLock")
+    private boolean mIsNotificationShowing = false;
+    @GuardedBy("mNotificationLock")
+    private UserHandle mNotificationUserHandle;
+
+    public CallStreamingNotification(@NonNull Context context,
+            @NonNull AppLabelProxy appLabelProxy,
+            @NonNull Executor asyncTaskExecutor) {
+        mContext = context;
+        mNotificationManager = context.getSystemService(NotificationManager.class);
+        mAppLabelProxy = appLabelProxy;
+        mAsyncTaskExecutor = asyncTaskExecutor;
+    }
+
+    @Override
+    public void onCallAdded(Call call) {
+        if (call.isStreaming()) {
+            trackStreamingCall(call);
+            enqueueStreamingNotification(call);
+        }
+    }
+
+    @Override
+    public void onCallRemoved(Call call) {
+        if (call == mStreamingCall) {
+            trackStreamingCall(null);
+            dequeueStreamingNotification();
+        }
+    }
+
+    /**
+     * Handles streaming state changes for a call.
+     * @param call the call
+     * @param isStreaming whether it is streaming or not
+     */
+    @Override
+    public void onCallStreamingStateChanged(Call call, boolean isStreaming) {
+        Log.i(this, "onCallStreamingStateChanged: call=%s, isStreaming=%b", call.getId(),
+                isStreaming);
+
+        if (isStreaming) {
+            trackStreamingCall(call);
+            enqueueStreamingNotification(call);
+        } else {
+            trackStreamingCall(null);
+            dequeueStreamingNotification();
+        }
+    }
+
+    /**
+     * Change the streaming call we are tracking.
+     * @param call the call.
+     */
+    private void trackStreamingCall(Call call) {
+        if (mStreamingCall != null) {
+            mStreamingCall.removeListener(this);
+        }
+        mStreamingCall = call;
+        if (mStreamingCall != null) {
+            mStreamingCall.addListener(this);
+        }
+    }
+
+    /**
+     * Enqueue an async task to post/repost the streaming notification.
+     * Note: This happens INSIDE the telecom lock.
+     * @param call the call to post notification for.
+     */
+    private void enqueueStreamingNotification(Call call) {
+        final Bitmap contactPhotoBitmap = call.getPhotoIcon();
+        mAsyncTaskExecutor.execute(() -> {
+            Icon contactPhotoIcon = null;
+            try {
+                contactPhotoIcon = Icon.createWithResource(mContext.getResources(),
+                        R.drawable.person_circle);
+            } catch (Exception e) {
+                // All loads of things can do wrong when working with bitmaps and images, so to
+                // ensure Telecom doesn't crash, lets try/catch to be sure.
+                Log.e(this, e, "enqueueStreamingNotification: Couldn't build avatar icon");
+            }
+            showStreamingNotification(call.getId(),
+                    call.getAssociatedUser(), call.getCallerDisplayName(),
+                    call.getHandle(), contactPhotoIcon,
+                    call.getTargetPhoneAccount().getComponentName().getPackageName(),
+                    call.getConnectTimeMillis());
+        });
+    }
+
+    /**
+     * Dequeues the call streaming notification.
+     * Note: This is yo be called within the Telecom sync lock to launch the task to remove the call
+     * streaming notification.
+     */
+    private void dequeueStreamingNotification() {
+        mAsyncTaskExecutor.execute(() -> hideStreamingNotification());
+    }
+
+    /**
+     * Show the call streaming notification.  This is intended to run outside the Telecom sync lock.
+     *
+     * @param callId the call ID we're streaming.
+     * @param userHandle the userhandle for the call.
+     * @param callerName the name of the caller/callee associated with the call
+     * @param callerAddress the address associated with the caller/callee
+     * @param photoIcon the contact photo icon if available
+     * @param appPackageName the package name for the app to post the notification for
+     * @param connectTimeMillis when the call connected (for chronometer in the notification)
+     */
+    private void showStreamingNotification(final String callId, final UserHandle userHandle,
+            String callerName, Uri callerAddress, Icon photoIcon, String appPackageName,
+            long connectTimeMillis) {
+        Log.i(this, "showStreamingNotification; callid=%s, hasPhoto=%b", callId, photoIcon != null);
+
+        // Use the caller name for the label if available, default to app name if none.
+        if (TextUtils.isEmpty(callerName)) {
+            // App did not provide a caller name, so default to app's name.
+            callerName = mAppLabelProxy.getAppLabel(appPackageName).toString();
+        }
+
+        // Action to hangup; this can use the default hangup action from the call style
+        // notification.
+        Intent hangupIntent = new Intent(TelecomBroadcastIntentProcessor.ACTION_HANGUP_CALL,
+                Uri.fromParts(CALL_ID_SCHEME, callId, null),
+                mContext, TelecomBroadcastReceiver.class);
+        PendingIntent hangupPendingIntent = PendingIntent.getBroadcast(mContext, 0, hangupIntent,
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+
+        // Action to switch here.
+        Intent switchHereIntent = new Intent(TelecomBroadcastIntentProcessor.ACTION_STOP_STREAMING,
+                Uri.fromParts(CALL_ID_SCHEME, callId, null),
+                mContext, TelecomBroadcastReceiver.class);
+        PendingIntent switchHerePendingIntent = PendingIntent.getBroadcast(mContext, 0,
+                switchHereIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+
+        // Apply a span to the string to colorize it using the "answer" color.
+        Spannable spannable = new SpannableString(
+                mContext.getString(R.string.call_streaming_notification_action_switch_here));
+        spannable.setSpan(new ForegroundColorSpan(
+                com.android.internal.R.color.call_notification_answer_color), 0, spannable.length(),
+                Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
+
+        // Use the "phone link" icon per mock.
+        Icon switchHereIcon = Icon.createWithResource(mContext, R.drawable.gm_phonelink);
+        Notification.Action.Builder switchHereBuilder = new Notification.Action.Builder(
+                switchHereIcon,
+                spannable,
+                switchHerePendingIntent);
+        Notification.Action switchHereAction = switchHereBuilder.build();
+
+        // Notifications use a "person" entity to identify caller/callee.
+        Person.Builder personBuilder = new Person.Builder()
+                .setName(callerName);
+
+        // Some apps use phone numbers to identify; these are something the notification framework
+        // can lookup in contacts to provide more data
+        if (callerAddress != null && PhoneAccount.SCHEME_TEL.equals(callerAddress)) {
+            personBuilder.setUri(callerAddress.toString());
+        }
+        if (photoIcon != null) {
+            personBuilder.setIcon(photoIcon);
+        }
+        Person person = personBuilder.build();
+
+        // Call Style notification requires a full screen intent, so we'll just link in a null
+        // pending intent
+        Intent nullIntent = new Intent();
+        PendingIntent nullPendingIntent = PendingIntent.getBroadcast(mContext, 0, nullIntent,
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+
+        Notification.Builder builder = new Notification.Builder(mContext,
+                NotificationChannelManager.CHANNEL_ID_CALL_STREAMING)
+                // Use call style to get the general look and feel for the notification; it provides
+                // a hangup action with the right action already so we can leverage that.  The
+                // "switch here" action will be a custom action defined later.
+                .setStyle(Notification.CallStyle.forOngoingCall(person, hangupPendingIntent))
+                .setSmallIcon(R.drawable.ic_phone)
+                .setContentText(mContext.getString(
+                        R.string.call_streaming_notification_body))
+                // Report call time
+                .setWhen(connectTimeMillis)
+                .setShowWhen(true)
+                .setUsesChronometer(true)
+                // Set the full screen intent; this is just tricking notification manager into
+                // letting us use this style.  Sssh.
+                .setFullScreenIntent(nullPendingIntent, true)
+                .setColorized(true)
+                .addAction(switchHereAction);
+        Notification notification = builder.build();
+
+        synchronized(mNotificationLock) {
+            mIsNotificationShowing = true;
+            mNotificationUserHandle = userHandle;
+            try {
+                mNotificationManager.notifyAsUser(NOTIFICATION_TAG, STREAMING_NOTIFICATION_ID,
+                        notification, userHandle);
+            } catch (Exception e) {
+                // We don't want to crash Telecom if something changes with the requirements for the
+                // notification.
+                Log.e(this, e, "Notification post failed.");
+            }
+        }
+    }
+
+    /**
+     * Removes the posted streaming notification.  Intended to run outside the telecom lock.
+     */
+    private void hideStreamingNotification() {
+        Log.i(this, "hideStreamingNotification");
+        synchronized(mNotificationLock) {
+            if (mIsNotificationShowing) {
+                mIsNotificationShowing = false;
+                mNotificationManager.cancelAsUser(NOTIFICATION_TAG,
+                        STREAMING_NOTIFICATION_ID, mNotificationUserHandle);
+            }
+        }
+    }
+
+    public static Bitmap drawableToBitmap(@Nullable Drawable drawable, int width, int height) {
+        if (drawable == null) {
+            return null;
+        }
+
+        Bitmap bitmap;
+        if (drawable instanceof BitmapDrawable) {
+            bitmap = ((BitmapDrawable) drawable).getBitmap();
+        } else {
+            if (width > 0 || height > 0) {
+                bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+            } else if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
+                // Needed for drawables that are just a colour.
+                bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
+            } else {
+                bitmap =
+                        Bitmap.createBitmap(
+                                drawable.getIntrinsicWidth(),
+                                drawable.getIntrinsicHeight(),
+                                Bitmap.Config.ARGB_8888);
+            }
+
+            Canvas canvas = new Canvas(bitmap);
+            drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+            drawable.draw(canvas);
+        }
+        return bitmap;
+    }
+}
diff --git a/src/com/android/server/telecom/ui/DisconnectedCallNotifier.java b/src/com/android/server/telecom/ui/DisconnectedCallNotifier.java
index 66f9fe4..1604285 100644
--- a/src/com/android/server/telecom/ui/DisconnectedCallNotifier.java
+++ b/src/com/android/server/telecom/ui/DisconnectedCallNotifier.java
@@ -143,8 +143,7 @@
                 DisconnectCause.REASON_EMERGENCY_CALL_PLACED.equals(cause.getReason())) {
             // Clear any existing notification.
             clearNotification(mCallsManager.getCurrentUserHandle());
-            UserHandle userHandle = call.getTargetPhoneAccount() != null ?
-                    call.getTargetPhoneAccount().getUserHandle() : call.getInitiatingUser();
+            UserHandle userHandle = call.getAssociatedUser();
             // As a last resort, use the current user to display the notification.
             if (userHandle == null) userHandle = mCallsManager.getCurrentUserHandle();
             mPendingCallNotification = new CallInfo(userHandle, call.getHandle(),
diff --git a/src/com/android/server/telecom/ui/IncomingCallNotifier.java b/src/com/android/server/telecom/ui/IncomingCallNotifier.java
index 0c1c5a3..d419163 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.getAssociatedUser());
             }
         }
     }
 
     private void showIncomingCallNotification(Call call) {
-        Log.i(this, "showIncomingCallNotification showCall = %s", call);
+        Log.i(this, "showIncomingCallNotification showCall = %s for user = %s",
+                call, call.getAssociatedUser());
 
         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.getAssociatedUser());
     }
 
-    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/ui/NotificationChannelManager.java b/src/com/android/server/telecom/ui/NotificationChannelManager.java
index 58794a6..b3cb2c3 100644
--- a/src/com/android/server/telecom/ui/NotificationChannelManager.java
+++ b/src/com/android/server/telecom/ui/NotificationChannelManager.java
@@ -40,6 +40,7 @@
     public static final String CHANNEL_ID_AUDIO_PROCESSING = "TelecomBackgroundAudioProcessing";
     public static final String CHANNEL_ID_DISCONNECTED_CALLS = "TelecomDisconnectedCalls";
     public static final String CHANNEL_ID_IN_CALL_SERVICE_CRASH = "TelecomInCallServiceCrash";
+    public static final String CHANNEL_ID_CALL_STREAMING = "TelecomCallStreaming";
 
     private BroadcastReceiver mLocaleChangeReceiver = new BroadcastReceiver() {
         @Override
@@ -50,8 +51,9 @@
     };
 
     public void createChannels(Context context) {
-        context.registerReceiver(mLocaleChangeReceiver,
-                new IntentFilter(Intent.ACTION_LOCALE_CHANGED));
+        IntentFilter localeChangedfilter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED);
+        localeChangedfilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
+        context.registerReceiver(mLocaleChangeReceiver, localeChangedfilter);
 
         createOrUpdateAll(context);
     }
@@ -63,6 +65,7 @@
         createOrUpdateChannel(context, CHANNEL_ID_AUDIO_PROCESSING);
         createOrUpdateChannel(context, CHANNEL_ID_DISCONNECTED_CALLS);
         createOrUpdateChannel(context, CHANNEL_ID_IN_CALL_SERVICE_CRASH);
+        createOrUpdateChannel(context, CHANNEL_ID_CALL_STREAMING);
     }
 
     private void createOrUpdateChannel(Context context, String channelId) {
@@ -127,6 +130,14 @@
                 lights = true;
                 vibration = true;
                 sound = null;
+            case CHANNEL_ID_CALL_STREAMING:
+                name = context.getText(R.string.notification_channel_call_streaming);
+                importance = NotificationManager.IMPORTANCE_DEFAULT;
+                canShowBadge = false;
+                lights = false;
+                vibration = false;
+                sound = null;
+                break;
         }
 
         NotificationChannel channel = new NotificationChannel(channelId, name, importance);
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..93d9836
--- /dev/null
+++ b/src/com/android/server/telecom/voip/CallEventCallbackAckTransaction.java
@@ -0,0 +1,144 @@
+/*
+ * 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
+                Log.i(TAG, String.format("CallEventCallbackAckTransaction:"
+                        + " client failed to complete the [%s] transaction", mAction));
+                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..d35030c
--- /dev/null
+++ b/src/com/android/server/telecom/voip/IncomingCallTransaction.java
@@ -0,0 +1,89 @@
+/*
+ * 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 static android.telecom.CallAttributes.DISPLAY_NAME_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());
+        mExtras.putCharSequence(DISPLAY_NAME_KEY, callAttributes.getDisplayName());
+        mExtras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS,
+                callAttributes.getAddress());
+        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..b2625e6
--- /dev/null
+++ b/src/com/android/server/telecom/voip/OutgoingCallTransaction.java
@@ -0,0 +1,133 @@
+/*
+ * 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.CallAttributes.DISPLAY_NAME_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());
+        mExtras.putCharSequence(DISPLAY_NAME_KEY, callAttributes.getDisplayName());
+        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..6176087
--- /dev/null
+++ b/src/com/android/server/telecom/voip/ParallelTransaction.java
@@ -0,0 +1,109 @@
+/*
+ * 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.LoggedHandlerExecutor;
+import com.android.server.telecom.TelecomSystem;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+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
+        CompletableFuture<Void> future = new CompletableFuture<>();
+        mHandler.postDelayed(() -> future.complete(null), TIMEOUT_LIMIT);
+        future.thenApplyAsync((x) -> {
+            if (mCompleted.getAndSet(true)) {
+                return null;
+            }
+            if (mCompleteListener != null) {
+                mCompleteListener.onTransactionTimeout(mTransactionName);
+            }
+            finish();
+            return null;
+        }, new LoggedHandlerExecutor(mHandler, mTransactionName + "@" + hashCode()
+                + ".s", mLock));
+
+        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) {
+                                CompletableFuture.completedFuture(null).thenApplyAsync(
+                                        (x) -> {
+                                            VoipCallTransactionResult mainResult =
+                                                    new VoipCallTransactionResult(
+                                                            VoipCallTransactionResult.RESULT_FAILED,
+                                                            String.format(
+                                                                    "sub transaction %s failed",
+                                                                    transactionName));
+                                            mCompleteListener.onTransactionCompleted(mainResult,
+                                                    mTransactionName);
+                                            finish();
+                                            return null;
+                                        }, new LoggedHandlerExecutor(mHandler,
+                                                mTransactionName + "@" + hashCode()
+                                                        + ".oTC", mLock));
+                            } else {
+                                if (mCount.decrementAndGet() == 0) {
+                                    scheduleTransaction();
+                                }
+                            }
+                        }
+
+                        @Override
+                        public void onTransactionTimeout(String transactionName) {
+                            CompletableFuture.completedFuture(null).thenApplyAsync(
+                                    (x) -> {
+                                        VoipCallTransactionResult mainResult =
+                                                new VoipCallTransactionResult(
+                                                VoipCallTransactionResult.RESULT_FAILED,
+                                                String.format("sub transaction %s timed out",
+                                                        transactionName));
+                                        mCompleteListener.onTransactionCompleted(mainResult,
+                                                mTransactionName);
+                                        finish();
+                                        return null;
+                                    }, new LoggedHandlerExecutor(mHandler,
+                                            mTransactionName + "@" + hashCode()
+                                                    + ".oTT", mLock));
+                        }
+                    };
+            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..b35b471
--- /dev/null
+++ b/src/com/android/server/telecom/voip/SerialTransaction.java
@@ -0,0 +1,118 @@
+/*
+ * 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.LoggedHandlerExecutor;
+import com.android.server.telecom.TelecomSystem;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * 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
+        CompletableFuture<Void> future = new CompletableFuture<>();
+        mHandler.postDelayed(() -> future.complete(null), TIMEOUT_LIMIT);
+        future.thenApplyAsync((x) -> {
+            if (mCompleted.getAndSet(true)) {
+                return null;
+            }
+            if (mCompleteListener != null) {
+                mCompleteListener.onTransactionTimeout(mTransactionName);
+            }
+            finish();
+            return null;
+        }, new LoggedHandlerExecutor(mHandler, mTransactionName + "@" + hashCode()
+                + ".s", mLock));
+
+        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) {
+                                handleTransactionFailure();
+                                CompletableFuture.completedFuture(null).thenApplyAsync(
+                                        (x) -> {
+                                            VoipCallTransactionResult mainResult =
+                                                    new VoipCallTransactionResult(
+                                                            VoipCallTransactionResult.RESULT_FAILED,
+                                                            String.format(
+                                                                    "sub transaction %s failed",
+                                                                    transactionName));
+                                            mCompleteListener.onTransactionCompleted(mainResult,
+                                                    mTransactionName);
+                                            finish();
+                                            return null;
+                                        }, new LoggedHandlerExecutor(mHandler,
+                                                mTransactionName + "@" + hashCode()
+                                                        + ".oTC", mLock));
+                            } else {
+                                if (mSubTransactions.size() > 0) {
+                                    VoipCallTransaction transaction = mSubTransactions.remove(0);
+                                    transaction.setCompleteListener(this);
+                                    transaction.start();
+                                } else {
+                                    scheduleTransaction();
+                                }
+                            }
+                        }
+
+                        @Override
+                        public void onTransactionTimeout(String transactionName) {
+                            handleTransactionFailure();
+                            CompletableFuture.completedFuture(null).thenApplyAsync(
+                                    (x) -> {
+                                        VoipCallTransactionResult mainResult =
+                                                new VoipCallTransactionResult(
+                                                VoipCallTransactionResult.RESULT_FAILED,
+                                                String.format("sub transaction %s timed out",
+                                                        transactionName));
+                                        mCompleteListener.onTransactionCompleted(mainResult,
+                                                mTransactionName);
+                                        finish();
+                                        return null;
+                                    }, new LoggedHandlerExecutor(mHandler,
+                                            mTransactionName + "@" + hashCode()
+                                                    + ".oTT", mLock));
+                        }
+                    };
+            VoipCallTransaction transaction = mSubTransactions.remove(0);
+            transaction.setCompleteListener(subTransactionListener);
+            transaction.start();
+        } else {
+            scheduleTransaction();
+        }
+    }
+
+    public void handleTransactionFailure() {}
+}
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..228bdde
--- /dev/null
+++ b/src/com/android/server/telecom/voip/TransactionManager.java
@@ -0,0 +1,130 @@
+/*
+ * 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.ArrayList;
+import java.util.List;
+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 %s completed: with result=[%d]",
+                        transactionName, 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){
+                Log.i(TAG, String.format("transaction %s timeout", 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() {
+        List<VoipCallTransaction> pendingTransactions;
+        synchronized (sLock) {
+            pendingTransactions = new ArrayList<>(mTransactions);
+        }
+        for (VoipCallTransaction transaction : pendingTransactions) {
+            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..3779a6d
--- /dev/null
+++ b/src/com/android/server/telecom/voip/VoipCallMonitor.java
@@ -0,0 +1,365 @@
+/*
+ * 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.LogUtils;
+import com.android.server.telecom.LoggedHandlerExecutor;
+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.Objects;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+
+public class VoipCallMonitor extends CallsManagerListenerBase {
+
+    private final List<Call> mNotificationPendingCalls;
+    // Same notification may be passed as different object in onNotificationPosted and
+    // onNotificationRemoved. Use its string as key to cache ongoing notifications.
+    private final Map<NotificationInfo, Call> mNotificationInfoToCallMap;
+    private final Map<PhoneAccountHandle, Set<Call>> mAccountHandleToCallMap;
+    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<NotificationInfo> mCachedNotifications;
+    private TelecomSystem.SyncRoot mSyncRoot;
+
+    public VoipCallMonitor(Context context, TelecomSystem.SyncRoot lock) {
+        mSyncRoot = lock;
+        mContext = context;
+        mHandlerThread = new HandlerThread(this.getClass().getSimpleName());
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+        mNotificationPendingCalls = new ArrayList<>();
+        mCachedNotifications = new ArrayList<>();
+        mNotificationInfoToCallMap = new HashMap<>();
+        mServices = new HashMap<>();
+        mAccountHandleToCallMap = 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)) {
+                        NotificationInfo info = new NotificationInfo(sbn.getPackageName(),
+                                sbn.getUser());
+                        boolean sbnMatched = false;
+                        for (Call call : mNotificationPendingCalls) {
+                            if (info.matchesCall(call)) {
+                                Log.i(this, "onNotificationPosted: found a pending "
+                                                + "callId=[%s] for the call notification w/ "
+                                                + "id=[%s]",
+                                        call.getId(), sbn.getId());
+                                mNotificationPendingCalls.remove(call);
+                                mNotificationInfoToCallMap.put(info, call);
+                                sbnMatched = true;
+                                break;
+                            }
+                        }
+                        if (!sbnMatched &&
+                                !mCachedNotifications.contains(info) /* don't re-add if update */) {
+                            Log.i(this, "onNotificationPosted: could not find a"
+                                            + "call for the call notification w/ id=[%s]",
+                                    sbn.getId());
+                            // notification may post before we started to monitor the call, cache
+                            // this notification and try to match it later with new added call.
+                            mCachedNotifications.add(info);
+                        }
+                    }
+                }
+            }
+
+            @Override
+            public void onNotificationRemoved(StatusBarNotification sbn) {
+                synchronized (mLock) {
+                    NotificationInfo info = new NotificationInfo(sbn.getPackageName(),
+                            sbn.getUser());
+                    mCachedNotifications.remove(info);
+                    if (mNotificationInfoToCallMap.isEmpty()) {
+                        return;
+                    }
+                    Call call = mNotificationInfoToCallMap.getOrDefault(info, null);
+                    if (call != null) {
+                        // TODO: fix potential bug for multiple calls of same voip app.
+                        mNotificationInfoToCallMap.remove(info, call);
+                        stopFGSDelegation(call);
+                    }
+                }
+            }
+        };
+
+    }
+
+    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 = mAccountHandleToCallMap.computeIfAbsent(phoneAccountHandle,
+                    k -> new HashSet<>());
+            callList.add(call);
+            CompletableFuture.completedFuture(null).thenComposeAsync(
+                    (x) -> {
+                        startFGSDelegation(call.getCallingPackageIdentity().mCallingPackagePid,
+                                call.getCallingPackageIdentity().mCallingPackageUid, call);
+                        return null;
+                    }, new LoggedHandlerExecutor(mHandler, "VCM.oCA", mSyncRoot));
+        }
+    }
+
+    @Override
+    public void onCallRemoved(Call call) {
+        if (!call.isTransactionalCall()) {
+            return;
+        }
+
+        synchronized (mLock) {
+            stopMonitorWorks(call);
+            PhoneAccountHandle phoneAccountHandle = call.getTargetPhoneAccount();
+            Set<Call> callList = mAccountHandleToCallMap.computeIfAbsent(phoneAccountHandle,
+                    k -> new HashSet<>());
+            callList.remove(call);
+
+            if (callList.isEmpty()) {
+                stopFGSDelegation(call);
+            }
+        }
+    }
+
+    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 {
+                if (mActivityManagerInternal
+                        .startForegroundServiceDelegate(options, fgsConnection)) {
+                    Log.addEvent(call, LogUtils.Events.GAINED_FGS_DELEGATION);
+                } else {
+                    Log.addEvent(call, LogUtils.Events.GAIN_FGS_DELEGATION_FAILED);
+                }
+            } catch (Exception e) {
+                Log.i(this, "startForegroundServiceDelegate failed due to: " + e);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    public void stopFGSDelegation(Call call) {
+        synchronized (mLock) {
+            Log.i(this, "stopFGSDelegation of call %s", call);
+            PhoneAccountHandle handle = call.getTargetPhoneAccount();
+            Set<Call> calls = mAccountHandleToCallMap.get(handle);
+
+            // Every call for the package that is losing foreground service delegation should be
+            // removed from tracking maps/contains in this class
+            if (calls != null) {
+                for (Call c : calls) {
+                    stopMonitorWorks(c); // remove the call from tacking in this class
+                }
+            }
+
+            mAccountHandleToCallMap.remove(handle);
+
+            if (mActivityManagerInternal != null) {
+                ServiceConnection fgsConnection = mServices.get(handle);
+                if (fgsConnection != null) {
+                    mActivityManagerInternal.stopForegroundServiceDelegate(fgsConnection);
+                    Log.addEvent(call, LogUtils.Events.LOST_FGS_DELEGATION);
+                }
+            }
+        }
+    }
+
+    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 (NotificationInfo info : mCachedNotifications) {
+                if (info.matchesCall(call)) {
+                    Log.i(this, "startMonitorNotification: found a cached call "
+                            + "notification for call=[%s]", call);
+                    mCachedNotifications.remove(info);
+                    mNotificationInfoToCallMap.put(info, call);
+                    sbnMatched = true;
+                    break;
+                }
+            }
+            if (!sbnMatched) {
+                // Only continue to
+                Log.i(this, "startMonitorNotification: could not find a call"
+                        + " notification for the call=[%s];", call);
+                mNotificationPendingCalls.add(call);
+                CompletableFuture<Void> future = new CompletableFuture<>();
+                mHandler.postDelayed(() -> future.complete(null), 5000L);
+                future.thenComposeAsync(
+                        (x) -> {
+                            if (mNotificationPendingCalls.contains(call)) {
+                                Log.i(this, "Notification for voip-call %s haven't "
+                                        + "posted in time, stop delegation.", call.getId());
+                                stopFGSDelegation(call);
+                                mNotificationPendingCalls.remove(call);
+                                return null;
+                            }
+                            return null;
+                        }, new LoggedHandlerExecutor(mHandler, "VCM.sMN", mSyncRoot));
+            }
+        }
+    }
+
+    private void stopMonitorNotification(Call call) {
+        mNotificationPendingCalls.remove(call);
+    }
+
+    @VisibleForTesting
+    public void setActivityManagerInternal(ActivityManagerInternal ami) {
+        mActivityManagerInternal = ami;
+    }
+
+    private static class NotificationInfo extends Object {
+        private String mPackageName;
+        private UserHandle mUserHandle;
+
+        NotificationInfo(String packageName, UserHandle userHandle) {
+            mPackageName = packageName;
+            mUserHandle = userHandle;
+        }
+
+        boolean matchesCall(Call call) {
+            PhoneAccountHandle accountHandle = call.getTargetPhoneAccount();
+            return mPackageName != null && mPackageName.equals(
+                    accountHandle.getComponentName().getPackageName())
+                    && mUserHandle != null && mUserHandle.equals(accountHandle.getUserHandle());
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (!(obj instanceof NotificationInfo)) {
+                return false;
+            }
+            NotificationInfo that = (NotificationInfo) obj;
+            return Objects.equals(this.mPackageName, that.mPackageName)
+                    && Objects.equals(this.mUserHandle, that.mUserHandle);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mPackageName, mUserHandle);
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("{ NotificationInfo: [mPackageName: ")
+                    .append(mPackageName)
+                    .append("], [mUserHandle=")
+                    .append(mUserHandle)
+                    .append("]  }");
+            return sb.toString();
+        }
+    }
+
+    @VisibleForTesting
+    public void postNotification(StatusBarNotification statusBarNotification) {
+        mNotificationListener.onNotificationPosted(statusBarNotification);
+    }
+
+    @VisibleForTesting
+    public void removeNotification(StatusBarNotification statusBarNotification) {
+        mNotificationListener.onNotificationRemoved(statusBarNotification);
+    }
+
+    @VisibleForTesting
+    public Set<Call> getCallsForHandle(PhoneAccountHandle handle){
+        return mAccountHandleToCallMap.get(handle);
+    }
+}
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..a952eb1
--- /dev/null
+++ b/src/com/android/server/telecom/voip/VoipCallTransaction.java
@@ -0,0 +1,110 @@
+/*
+ * 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 android.telecom.Log;
+
+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;
+    protected 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
+        CompletableFuture<Void> future = new CompletableFuture<>();
+        mHandler.postDelayed(() -> future.complete(null), TIMEOUT_LIMIT);
+        future.thenApplyAsync((x) -> {
+            if (mCompleted.getAndSet(true)) {
+                return null;
+            }
+            if (mCompleteListener != null) {
+                mCompleteListener.onTransactionTimeout(mTransactionName);
+            }
+            finish();
+            return null;
+        }, new LoggedHandlerExecutor(mHandler, mTransactionName + "@" + hashCode()
+                + ".s", mLock));
+
+        scheduleTransaction();
+    }
+
+    protected void scheduleTransaction() {
+        LoggedHandlerExecutor executor = new LoggedHandlerExecutor(mHandler,
+                mTransactionName + "@" + hashCode() + ".pT", mLock);
+        CompletableFuture<Void> future = CompletableFuture.completedFuture(null);
+        future.thenComposeAsync(this::processTransaction, executor)
+                .thenApplyAsync((Function<VoipCallTransactionResult, Void>) result -> {
+                    mCompleted.set(true);
+                    if (mCompleteListener != null) {
+                        mCompleteListener.onTransactionCompleted(result, mTransactionName);
+                    }
+                    finish();
+                    return null;
+                    }, executor)
+                .exceptionallyAsync((throwable -> {
+                    Log.e(this, throwable, "Error while executing transaction.");
+                    return null;
+                }), executor);
+    }
+
+    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/AndroidManifest.xml b/testapps/AndroidManifest.xml
index dd8258a..645a42b 100644
--- a/testapps/AndroidManifest.xml
+++ b/testapps/AndroidManifest.xml
@@ -259,6 +259,15 @@
           </intent-filter>
         </service>
 
+        <service android:name="com.android.server.telecom.testapps.OtherSelfManagedConnectionService"
+                 android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
+                 android:process="com.android.server.telecom.testapps.SelfMangingCallingApp"
+                 android:exported="true">
+            <intent-filter>
+                <action android:name="android.telecom.ConnectionService"/>
+            </intent-filter>
+        </service>
+
         <receiver android:exported="false"
              android:process="com.android.server.telecom.testapps.SelfMangingCallingApp"
              android:name="com.android.server.telecom.testapps.SelfManagedCallNotificationReceiver"/>
diff --git a/testapps/res/layout/self_managed_sample_main.xml b/testapps/res/layout/self_managed_sample_main.xml
index d26d629..98b879a 100644
--- a/testapps/res/layout/self_managed_sample_main.xml
+++ b/testapps/res/layout/self_managed_sample_main.xml
@@ -55,6 +55,12 @@
                 android:layout_height="wrap_content"
                 android:background="@color/test_call_b_color"
                 android:text="2"/>
+            <RadioButton
+                android:id="@+id/useAcct3Button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:background="@color/test_call_c_color"
+                android:text="3"/>
         </RadioGroup>
         <TextView
             android:id="@+id/hasFocus"
diff --git a/testapps/res/values/colors.xml b/testapps/res/values/colors.xml
index 3939e78..9447ac8 100644
--- a/testapps/res/values/colors.xml
+++ b/testapps/res/values/colors.xml
@@ -17,4 +17,5 @@
 <resources>
     <color name="test_call_a_color">#f2eebf</color>
     <color name="test_call_b_color">#afc5e6</color>
+    <color name="test_call_c_color">#c5afe6</color>
 </resources>
diff --git a/testapps/src/com/android/server/telecom/testapps/OtherSelfManagedConnectionService.java b/testapps/src/com/android/server/telecom/testapps/OtherSelfManagedConnectionService.java
new file mode 100644
index 0000000..7bb9830
--- /dev/null
+++ b/testapps/src/com/android/server/telecom/testapps/OtherSelfManagedConnectionService.java
@@ -0,0 +1,20 @@
+/*
+ * 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.testapps;
+
+public class OtherSelfManagedConnectionService extends SelfManagedConnectionService {
+}
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
index d4661ff..273b060 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallList.java
@@ -46,20 +46,27 @@
 
     public static String SELF_MANAGED_ACCOUNT_1 = "1";
     public static String SELF_MANAGED_ACCOUNT_2 = "2";
+    public static String SELF_MANAGED_ACCOUNT_1A = "1A";
     public static String SELF_MANAGED_ACCOUNT_3 = "3";
     public static String SELF_MANAGED_NAME_1 = "SuperCall";
     public static String SELF_MANAGED_NAME_2 = "Mega Call";
-    public static String SELF_MANAGED_NAME_3 = "SM Call";
+    public static String SELF_MANAGED_NAME_1A = "SM Call";
+    public static String SELF_MANAGED_NAME_3 = "Sep Process";
     public static String CUSTOM_URI_SCHEME = "custom";
 
     private static SelfManagedCallList sInstance;
     private static ComponentName COMPONENT_NAME = new ComponentName(
             SelfManagedCallList.class.getPackage().getName(),
             SelfManagedConnectionService.class.getName());
+    private static ComponentName OTHER_COMPONENT_NAME = new ComponentName(
+            SelfManagedCallList.class.getPackage().getName(),
+            OtherSelfManagedConnectionService.class.getName());
     private static Uri SELF_MANAGED_ADDRESS_1 = Uri.fromParts(PhoneAccount.SCHEME_TEL, "555-1212",
             "");
     private static Uri SELF_MANAGED_ADDRESS_2 = Uri.fromParts(PhoneAccount.SCHEME_SIP,
             "me@test.org", "");
+    private static Uri SELF_MANAGED_ADDRESS_3 = Uri.fromParts(PhoneAccount.SCHEME_SIP,
+            "hilda@test.org", "");
     private static Map<String, PhoneAccountHandle> mPhoneAccounts = new ArrayMap();
 
     public static SelfManagedCallList getInstance() {
@@ -101,20 +108,29 @@
                 SELF_MANAGED_NAME_1, true /* areCallsLogged */);
         registerPhoneAccount(context, SELF_MANAGED_ACCOUNT_2, SELF_MANAGED_ADDRESS_2,
                 SELF_MANAGED_NAME_2, false /* areCallsLogged */);
-        registerPhoneAccount(context, SELF_MANAGED_ACCOUNT_3, SELF_MANAGED_ADDRESS_1,
-                SELF_MANAGED_NAME_3, true /* areCallsLogged */);
+        registerPhoneAccount(context, SELF_MANAGED_ACCOUNT_1A, SELF_MANAGED_ADDRESS_1,
+                SELF_MANAGED_NAME_1A, true /* areCallsLogged */);
+        registerPhoneAccount(context, SELF_MANAGED_ACCOUNT_1A, SELF_MANAGED_ADDRESS_1,
+                SELF_MANAGED_NAME_1A, true /* areCallsLogged */);
+        registerPhoneAccount(context, OTHER_COMPONENT_NAME, SELF_MANAGED_ACCOUNT_3,
+                SELF_MANAGED_ADDRESS_3, SELF_MANAGED_NAME_3, false /* areCallsLogged */);
     }
 
     public void registerPhoneAccount(Context context, String id, Uri address, String name,
-                                     boolean areCallsLogged) {
-        PhoneAccountHandle handle = new PhoneAccountHandle(COMPONENT_NAME, id);
+            boolean areCallsLogged) {
+        registerPhoneAccount(context, COMPONENT_NAME, id, address, name, areCallsLogged);
+    }
+
+    public void registerPhoneAccount(Context context, ComponentName componentName, String id,
+            Uri address, String name, boolean areCallsLogged) {
+        PhoneAccountHandle handle = new PhoneAccountHandle(componentName, id);
         mPhoneAccounts.put(id, handle);
         Bundle extras = new Bundle();
         extras.putBoolean(PhoneAccount.EXTRA_SUPPORTS_HANDOVER_TO, true);
         if (areCallsLogged) {
             extras.putBoolean(PhoneAccount.EXTRA_LOG_SELF_MANAGED_CALLS, true);
         }
-        if (id.equals(SELF_MANAGED_ACCOUNT_3)) {
+        if (id.equals(SELF_MANAGED_ACCOUNT_1A)) {
             extras.putBoolean(PhoneAccount.EXTRA_ADD_SELF_MANAGED_CALLS_TO_INCALLSERVICE, true);
         }
         PhoneAccount.Builder builder = PhoneAccount.builder(handle, name)
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java
index 75ceb62..475f255 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallListAdapter.java
@@ -166,8 +166,10 @@
                 SelfManagedConnection.EXTRA_PHONE_ACCOUNT_HANDLE);
         if (phoneAccountHandle.getId().equals(SelfManagedCallList.SELF_MANAGED_ACCOUNT_1)) {
             result.setBackgroundColor(result.getContext().getColor(R.color.test_call_a_color));
-        } else {
+        } else if (phoneAccountHandle.getId().equals(SelfManagedCallList.SELF_MANAGED_ACCOUNT_2)) {
             result.setBackgroundColor(result.getContext().getColor(R.color.test_call_b_color));
+        } else {
+            result.setBackgroundColor(result.getContext().getColor(R.color.test_call_c_color));
         }
 
         CallAudioState audioState = connection.getCallAudioState();
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
index 44410d2..5cdaf3d 100644
--- a/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
+++ b/testapps/src/com/android/server/telecom/testapps/SelfManagedCallingActivity.java
@@ -43,8 +43,6 @@
 import android.widget.TextView;
 import android.widget.Toast;
 
-import com.android.server.telecom.testapps.R;
-
 import java.util.Objects;
 
 /**
@@ -66,6 +64,7 @@
     private Button mDisableCarMode;
     private RadioButton mUseAcct1Button;
     private RadioButton mUseAcct2Button;
+    private RadioButton mUseAcct3Button;
     private CheckBox mHoldableCheckbox;
     private CheckBox mVideoCallCheckbox;
     private EditText mNumber;
@@ -165,6 +164,7 @@
         }));
         mUseAcct1Button = findViewById(R.id.useAcct1Button);
         mUseAcct2Button = findViewById(R.id.useAcct2Button);
+        mUseAcct3Button = findViewById(R.id.useAcct3Button);
         mHasFocus = findViewById(R.id.hasFocus);
         mVideoCallCheckbox = findViewById(R.id.videoCall);
         mHoldableCheckbox = findViewById(R.id.holdable);
@@ -183,6 +183,8 @@
             return mCallList.getPhoneAccountHandle(SelfManagedCallList.SELF_MANAGED_ACCOUNT_1);
         } else if (mUseAcct2Button.isChecked()) {
             return mCallList.getPhoneAccountHandle(SelfManagedCallList.SELF_MANAGED_ACCOUNT_2);
+        } else if (mUseAcct3Button.isChecked()) {
+            return mCallList.getPhoneAccountHandle(SelfManagedCallList.SELF_MANAGED_ACCOUNT_3);
         }
         return null;
     }
@@ -214,8 +216,7 @@
 
     private void placeSelfManagedOutgoingCall() {
         TelecomManager tm = TelecomManager.from(this);
-        PhoneAccountHandle phoneAccountHandle = mCallList.getPhoneAccountHandle(
-                SelfManagedCallList.SELF_MANAGED_ACCOUNT_3);
+        PhoneAccountHandle phoneAccountHandle = getSelectedPhoneAccountHandle();
 
         if (mCheckIfPermittedBeforeCalling.isChecked()) {
             Toast.makeText(this, R.string.outgoingCallNotPermitted, Toast.LENGTH_SHORT).show();
@@ -264,7 +265,7 @@
     private void placeSelfManagedIncomingCall() {
         TelecomManager tm = TelecomManager.from(this);
         PhoneAccountHandle phoneAccountHandle = mCallList.getPhoneAccountHandle(
-                SelfManagedCallList.SELF_MANAGED_ACCOUNT_3);
+                SelfManagedCallList.SELF_MANAGED_ACCOUNT_1A);
 
         if (mCheckIfPermittedBeforeCalling.isChecked()) {
             if (!tm.isIncomingCallPermitted(phoneAccountHandle)) {
diff --git a/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java b/testapps/src/com/android/server/telecom/testapps/SelfManagedConnectionService.java
index e6e35d8..3ef8fbb 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) {
diff --git a/testapps/streamingtest/Android.bp b/testapps/streamingtest/Android.bp
new file mode 100644
index 0000000..bd0a582
--- /dev/null
+++ b/testapps/streamingtest/Android.bp
@@ -0,0 +1,31 @@
+//
+// 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: "streamingTestApp",
+    static_libs: [
+        "androidx.legacy_legacy-support-v4",
+        "guava",
+    ],
+    srcs: ["src/**/*.java"],
+    platform_apis: true,
+    certificate: "platform",
+    privileged: true,
+}
diff --git a/testapps/streamingtest/AndroidManifest.xml b/testapps/streamingtest/AndroidManifest.xml
new file mode 100644
index 0000000..47e4abc
--- /dev/null
+++ b/testapps/streamingtest/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          coreApp="true"
+          package="com.android.server.telecom.streamingtest">
+
+    <uses-sdk android:minSdkVersion="28"
+              android:targetSdkVersion="33"/>
+
+    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+    <uses-permission android:name="android.permission.CALL_AUDIO_INTERCEPTION"/>
+
+    <application android:label="Streaming Test App">
+        <uses-library android:name="android.test.runner"/>
+
+        <service android:name="com.android.server.telecom.streamingtest.StreamingService"
+                 android:exported="true"
+                 android:permission="android.permission.BIND_CALL_STREAMING_SERVICE">
+            <intent-filter>
+                <action android:name="android.telecom.CallStreamingService"/>
+            </intent-filter>
+        </service>
+    </application>
+</manifest>
diff --git a/testapps/streamingtest/src/com/android/server/telecom/streamingtest/StreamingService.java b/testapps/streamingtest/src/com/android/server/telecom/streamingtest/StreamingService.java
new file mode 100644
index 0000000..c76b349
--- /dev/null
+++ b/testapps/streamingtest/src/com/android/server/telecom/streamingtest/StreamingService.java
@@ -0,0 +1,46 @@
+/*
+ * 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.streamingtest;
+
+import android.annotation.NonNull;
+import android.content.Intent;
+import android.telecom.CallStreamingService;
+import android.telecom.StreamingCall;
+import android.telecom.Log;
+
+public class StreamingService extends CallStreamingService {
+    @Override
+    public void onCallStreamingStarted(@NonNull StreamingCall call) {
+        Log.i(this, "onCallStreamingStarted: call %s", call);
+    }
+
+    @Override
+    public void onCallStreamingStopped() {
+        Log.i(this, "onCallStreamingStopped");
+    }
+
+    @Override
+    public void onCallStreamingStateChanged(int state) {
+        Log.i(this, "onCallStreamingStateChanged; state=%d", state);
+    }
+
+    @Override
+    public boolean onUnbind(Intent intent) {
+        Log.i(this, "onUnbind");
+        return false;
+    }
+}
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..e4968db
--- /dev/null
+++ b/testapps/transactionalVoipApp/AndroidManifest.xml
@@ -0,0 +1,62 @@
+<?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"/>
+    <!-- Needed to test media/audio -->
+    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+    <!-- Needed for foreground services -->
+    <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT"/>
+    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
+    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL"/>
+
+    <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>
+
+        <activity android:name="com.android.server.telecom.transactionalVoipApp.InCallActivity"
+                  android:exported="true"
+                  android:launchMode="singleInstance"
+                  android:label="InCall VoIP Activity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <service
+            android:name=".BackgroundIncomingCallService"
+            android:foregroundServiceType="phoneCall"
+            android:exported="false"
+        />
+
+    </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/in_call_activity.xml b/testapps/transactionalVoipApp/res/layout/in_call_activity.xml
new file mode 100644
index 0000000..a92a99b
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/layout/in_call_activity.xml
@@ -0,0 +1,110 @@
+<?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/getCallIdTextView"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/get_call_id"
+    />
+
+    <Button
+        android:id="@+id/updateCallStyleNotification"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/update_notification"
+    />
+
+    <Button
+        android:id="@+id/answer_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/answer"/>
+
+    <Button
+        android:id="@+id/set_call_active_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/set_call_active"/>
+
+    <Button
+        android:id="@+id/set_call_inactive_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/set_call_inactive"/>
+
+    <Button
+        android:id="@+id/disconnect_call_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/disconnect_call"/>
+
+    <Button
+        android:id="@+id/start_stream_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/start_stream"/>
+
+    <Button
+        android:id="@+id/crash_app"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/crash_app"/>
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+
+        <TextView
+            android:id="@+id/current_endpoint"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+        />
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+
+            <Button
+                android:id="@+id/request_earpiece"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/request_earpiece_endpoint"/>
+
+            <Button
+                android:id="@+id/request_speaker"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/request_speaker_endpoint"/>
+
+            <Button
+                android:id="@+id/request_bluetooth"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/request_bluetooth_endpoint"/>
+        </LinearLayout>
+
+    </LinearLayout>
+</LinearLayout>
diff --git a/testapps/transactionalVoipApp/res/layout/main_activity.xml b/testapps/transactionalVoipApp/res/layout/main_activity.xml
new file mode 100644
index 0000000..28f0744
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/layout/main_activity.xml
@@ -0,0 +1,68 @@
+<?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"/>
+
+        <Button
+            android:id="@+id/startForegroundService"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/start_foreground_service"
+        />
+
+        <LinearLayout
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+
+            <Button
+                android:id="@+id/startOutgoingCall"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/start_outgoing"
+            />
+
+            <Button
+                android:id="@+id/startIncomingCall"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/start_incoming"
+            />
+        </LinearLayout>
+    </LinearLayout>
+</LinearLayout>
diff --git a/testapps/transactionalVoipApp/res/raw/sample_audio.ogg b/testapps/transactionalVoipApp/res/raw/sample_audio.ogg
new file mode 100644
index 0000000..0129b46
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/raw/sample_audio.ogg
Binary files differ
diff --git a/testapps/transactionalVoipApp/res/raw/sample_audio2.ogg b/testapps/transactionalVoipApp/res/raw/sample_audio2.ogg
new file mode 100644
index 0000000..a0b39b4
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/raw/sample_audio2.ogg
Binary files differ
diff --git a/testapps/transactionalVoipApp/res/values-af/strings.xml b/testapps/transactionalVoipApp/res/values-af/strings.xml
new file mode 100644
index 0000000..efcbdc0
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-af/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Registreer foonrekening"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..b155a3a
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-am/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"የስልክ መለያ መዝግብ"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..e6c7e6f
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-ar/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"تسجيل حساب الهاتف"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..995624a
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-as/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"ফ\'নৰ একাউণ্ট পঞ্জীয়ন কৰক"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..db37f1b
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-az/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Telefon Hesabını Qeydiyyatdan Keçirin"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..9b02b9e
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Registruj nalog telefona"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..81b06d2
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-be/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Зарэгістраваць уліковы запіс тэлефона"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..283fc24
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-bg/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Регистриране на профила на телефона"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..c4266d0
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-bn/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"ফোনের অ্যাকাউন্ট রেজিস্টার করুন"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..f993930
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-bs/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Registrirajte račun telefona"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..37561c8
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-ca/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Registra el compte del telèfon"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..189a846
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-cs/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Registrovat telefonní účet"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..c7f9be4
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-da/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Registrer telefonkonto"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..f7719b9
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-de/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Telefonkonto registrieren"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..7c83600
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-el/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Εγγραφή λογαριασμού τηλεφώνου"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..abf4611
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-en-rAU/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Register phone account"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..5986f59
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-en-rCA/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Register Phone Account"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..abf4611
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-en-rGB/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Register phone account"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..abf4611
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-en-rIN/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Register phone account"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..764ebda
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-en-rXC/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‎‏‏‏‏‎‏‏‏‎‏‎‏‎‏‎‎‏‏‎‎‏‎‏‎‏‎‏‎‎‏‏‎‎‎‏‏‎‏‏‎‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‏‏‏‎‏‏‏‏‏‏‎‎‎Register Phone Account‎‏‎‎‏‎"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..588150c
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-es-rUS/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Registrar cuenta telefónica"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..0e290ae
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-es/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Registrar cuenta de teléfono"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..d9e9f97
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-et/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Telefonikonto registreerimine"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..379065e
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-eu/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Erregistratu telefonoaren kontua"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..eb7b88e
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-fa/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"ثبت حساب تلفن"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..d64dbe2
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-fi/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Rekisteröi puhelintili"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..173fc88
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-fr-rCA/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Inscrire un compte téléphonique"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..8fcbe7d
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-fr/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Enregistrer un compte de téléphonie"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..9f98e24
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-gl/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Rexistrar conta do teléfono"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..d1db7a1
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-gu/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"ફોન એકાઉન્ટ રજિસ્ટર કરો"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..c6c2efb
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-hi/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Phone Account में रजिस्टर करें"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..de2e01d
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-hr/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Registracija telefonskog računa"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..8b3b1bd
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-hu/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Telefonáláshoz használt fiók regisztrálása"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..9d19ce1
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-hy/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Հեռախոսի հաշվի գրանցում"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..3ab9c6f
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-in/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Daftarkan Akun Ponsel"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..18c115d
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-is/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Skrá símareikning"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..be0f1ec
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-it/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Registra account telefono"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..87d8cba
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-iw/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"רישום חשבון הטלפון"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..a924731
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-ja/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"スマートフォン アカウントを登録"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..10b82dc
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-ka/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"ტელეფონის ანგარიშის რეგისტრაცია"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..6dd0236
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-kk/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Телефон аккаунтын тіркеу"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..fadb82a
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-km/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"ចុះឈ្មោះ​គណនី​ទូរសព្ទ"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..21d5159
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-kn/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"ಫೋನ್ ಖಾತೆಯನ್ನು ನೋಂದಾಯಿಸಿ"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..efcce49
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-ko/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"전화 계정 등록"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..5e1abe4
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-ky/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Телефон аккаунтун каттоо"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..37ea968
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-lo/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"ລົງທະບຽນບັນຊີໂທລະສັບ"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..576887a
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-lt/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Užregistruoti telefono paskyrą"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..8982951
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-lv/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Reģistrēt tālruņa kontu"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..4e4fddc
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-mk/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Регистрирај телефонска сметка"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..1de2525
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-ml/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"ഫോൺ അക്കൗണ്ട് രജിസ്റ്റർ ചെയ്യുക"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..a8fa6dd
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-mn/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Утасны бүртгэл бүртгүүлэх"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..08a5a99
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-mr/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"फोन खात्याची नोंदणी करा"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..aed28d0
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-ms/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Daftar Akaun Telefon"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..b013242
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-my/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"ဖုန်းအကောင့် မှတ်ပုံတင်ရန်"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..50e83ab
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-nb/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Registrer telefonkonto"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..1934bd2
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-ne/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"फोन खाता दर्ता गर्नुहोस्"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..ae14093
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-nl/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Telefoonaccount registreren"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..30d64e2
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-or/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"ଫୋନ ଆକାଉଣ୍ଟର ପଞ୍ଜିକରଣ କରନ୍ତୁ"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..a5647f8
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-pa/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"ਫ਼ੋਨ ਖਾਤਾ ਰਜਿਸਟਰ ਕਰੋ"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..7318386
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-pl/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Zarejestruj konto telefonu"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..7bf7490
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-pt-rPT/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Registar conta do telemóvel"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..88ca27f
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-pt/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Registrar conta telefônica"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..9e713df
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-ro/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Înregistrează contul de telefon"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..68f5a84
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-ru/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Зарегистрировать аккаунт телефона"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..9ab0739
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-si/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"දුරකථන ගිණුම ලියාපදිංචි කරන්න"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..5b641e9
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-sk/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Registrovať telefónny účet"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..16f522d
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-sl/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Registracija telefonskega računa"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..975477e
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-sq/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Regjistro llogarinë e telefonit"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..dd5a3c0
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-sr/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Региструј налог телефона"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..f97e5a9
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-sv/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Registrera telefonkonto"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..acba9c3
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-sw/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Sajili Akaunti ya Simu"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..4a01a58
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-ta/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"மொபைல் கணக்கைப் பதிவுசெய்தல்"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..060086f
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-te/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"ఫోన్ ఖాతాను రిజిస్టర్ చేయండి"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..09ad2c5
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-th/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"ลงทะเบียนบัญชีของโทรศัพท์"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..fa0bfe5
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-tl/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Irehistro ang Phone Account"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..489f2e7
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-tr/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Telefon Hesabını Kaydet"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..ffae40d
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-uk/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Зареєструвати обліковий запис телефона"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..0f5de18
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-ur/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"فون کے اکاؤنٹ کو رجسٹر کریں"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..f12eac7
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-uz/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Telefon hisobini ro‘yxatdan o‘tkazish"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..a4b78a0
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-vi/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Đăng ký tài khoản điện thoại"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..3b64e5c
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-zh-rCN/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"注册电话帐号"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..5f03449
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-zh-rHK/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"註冊電話帳戶"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..aaad5a3
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-zh-rTW/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"註冊電話帳戶"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</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..199d54e
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values-zu/strings.xml
@@ -0,0 +1,48 @@
+<?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>
+    <!-- no translation found for in_call_activity_name (7545884666442897585) -->
+    <skip />
+    <string name="register_phone_account" msgid="1920315963082350332">"Bhalisa I-akhawunti Yefoni"</string>
+    <!-- no translation found for start_foreground_service (8968755699895128574) -->
+    <skip />
+    <!-- no translation found for start_outgoing (1441644037370361864) -->
+    <skip />
+    <!-- no translation found for start_incoming (6444983300186361271) -->
+    <skip />
+    <!-- no translation found for get_call_id (5513943242738347108) -->
+    <skip />
+    <!-- no translation found for set_call_active (3365404393507589899) -->
+    <skip />
+    <!-- no translation found for answer (5423590397665409939) -->
+    <skip />
+    <!-- no translation found for set_call_inactive (7106775211368705195) -->
+    <skip />
+    <!-- no translation found for disconnect_call (1349412380315371385) -->
+    <skip />
+    <!-- no translation found for request_earpiece_endpoint (6649571985089296573) -->
+    <skip />
+    <!-- no translation found for request_speaker_endpoint (1033259535289845405) -->
+    <skip />
+    <!-- no translation found for request_bluetooth_endpoint (5933254250623451836) -->
+    <skip />
+    <!-- no translation found for start_stream (3567634786280097431) -->
+    <skip />
+</resources>
diff --git a/testapps/transactionalVoipApp/res/values/strings.xml b/testapps/transactionalVoipApp/res/values/strings.xml
new file mode 100644
index 0000000..8239a0e
--- /dev/null
+++ b/testapps/transactionalVoipApp/res/values/strings.xml
@@ -0,0 +1,44 @@
+<?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="in_call_activity_name">Transactional In Call Activity</string>
+
+    <!-- Main Activity -->
+    <string name="register_phone_account">Register Phone Account</string>
+    <string name="start_foreground_service">Start FGS (simulate MT + app in background)</string>
+    <string name="start_outgoing">Start Outgoing Call</string>
+    <string name="start_incoming">Start Incoming Call</string>
+
+    <!-- InCall Activity -->
+    <string name="get_call_id">call id not set</string>
+    <!--  control the call state -->
+    <string name="set_call_active">setActive</string>
+    <string name="answer">answer</string>
+    <string name="set_call_inactive">setInactive</string>
+    <string name="disconnect_call">disconnect</string>
+    <!-- control the call audio -->
+    <string name="request_earpiece_endpoint">Earpiece</string>
+    <string name="request_speaker_endpoint">Speaker</string>
+    <string name="request_bluetooth_endpoint">Bluetooth</string>
+    <!-- extra functionality -->
+    <string name="start_stream">start streaming</string>
+    <string name="crash_app">throw exception</string>
+    <string name="update_notification"> update notification to ongoing call style</string>
+
+</resources>
\ No newline at end of file
diff --git a/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/BackgroundIncomingCallService.java b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/BackgroundIncomingCallService.java
new file mode 100644
index 0000000..b503e94
--- /dev/null
+++ b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/BackgroundIncomingCallService.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom.transactionalVoipApp;
+
+import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL;
+
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+import android.util.Log;
+
+public class BackgroundIncomingCallService extends Service {
+    // finals
+    private static final String TAG = "BackgroundIncomingCallService";
+    // instance vars
+    private NotificationManager mNotificationManager;
+    private final IBinder mBinder = new LocalBinder();
+
+    @Override
+    public void onCreate() {
+        Log.i(TAG, "onCreate");
+        mNotificationManager = getSystemService(NotificationManager.class);
+    }
+
+    @Override
+    @StartResult
+    public int onStartCommand(Intent intent, @StartArgFlags int flags, int startId) {
+        Log.i(TAG, String.format("onStartCommand: intent=[%s]", intent));
+
+        // create the notification channel
+        if (mNotificationManager != null) {
+            mNotificationManager.createNotificationChannel(new NotificationChannel(
+                    Utils.CHANNEL_ID, "incoming calls", NotificationManager.IMPORTANCE_DEFAULT));
+        }
+
+        // start the foreground service and post a notification
+        startForeground(98765, Utils.createCallStyleNotification(this),
+                FOREGROUND_SERVICE_TYPE_PHONE_CALL);
+
+        return Service.START_STICKY_COMPATIBILITY;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        Log.i(TAG, String.format("onBind: intent=[%s]", intent));
+        return mBinder;
+    }
+
+    /**
+     * Class used for the client Binder.  Because we know this service always
+     * runs in the same process as its clients, we don't need to deal with IPC.
+     */
+    public class LocalBinder extends Binder {
+        BackgroundIncomingCallService getService() {
+            // Return this instance of LocalService so clients can call public methods
+            return BackgroundIncomingCallService.this;
+        }
+    }
+}
diff --git a/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/InCallActivity.java b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/InCallActivity.java
new file mode 100644
index 0000000..50556a1
--- /dev/null
+++ b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/InCallActivity.java
@@ -0,0 +1,303 @@
+/*
+ * 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.transactionalVoipApp;
+
+import static android.telecom.CallAttributes.AUDIO_CALL;
+import static android.telecom.CallAttributes.DIRECTION_INCOMING;
+import static android.telecom.CallAttributes.DIRECTION_OUTGOING;
+
+import android.app.Activity;
+import android.media.AudioManager;
+import android.media.AudioRecord;
+import android.media.MediaPlayer;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.OutcomeReceiver;
+import android.telecom.CallAttributes;
+import android.telecom.CallControl;
+import android.telecom.CallEndpoint;
+import android.telecom.CallException;
+import android.telecom.DisconnectCause;
+import android.telecom.TelecomManager;
+import android.util.Log;
+import android.view.View;
+import android.widget.TextView;
+
+public class InCallActivity extends Activity {
+    private static final String TAG = "InCallActivity";
+    private final AudioManager.AudioRecordingCallback mAudioRecordingCallback =
+            Utils.getAudioRecordingCallback();
+    private static TelecomManager mTelecomManager;
+    private MyVoipCall mVoipCall;
+    private MediaPlayer mMediaPlayer;
+    private AudioRecord mAudioRecord;
+    private int mCallDirection = DIRECTION_INCOMING;
+    private TextView mCurrentEndpointTextView;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        Log.i(TAG, "#onCreate: in function");
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.in_call_activity);
+
+        Bundle extras = getIntent().getExtras();
+        // Copy the extras with properties like call direction into the extras so the below
+        // code can access them.
+        if (extras != null && extras.containsKey(Utils.sEXTRAS_KEY)) {
+            extras.putAll(extras.getBundle(Utils.sEXTRAS_KEY));
+        }
+        if (extras != null) {
+            mCallDirection = extras.getInt(Utils.sCALL_DIRECTION_KEY, DIRECTION_INCOMING);
+        }
+        mCurrentEndpointTextView = findViewById(R.id.current_endpoint);
+        mCurrentEndpointTextView.setText("Endpoint/Audio Route NOT ESTABLISHED");
+        updateCallId();
+        mTelecomManager = getSystemService(TelecomManager.class);
+        mMediaPlayer = Utils.createMediaPlayer(getApplicationContext());
+        mAudioRecord = Utils.createAudioRecord();
+        mAudioRecord.registerAudioRecordingCallback(Runnable::run, mAudioRecordingCallback);
+
+        if (mVoipCall == null) {
+            addCall();
+        }
+
+        findViewById(R.id.set_call_active_button).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                updateCurrentEndpoint();
+                if (canUseCallControl()) {
+                    mVoipCall.mCallControl.setActive(Runnable::run,
+                            Utils.getLoggableOutcomeReceiver("setActive"));
+                }
+                mAudioRecord.startRecording();
+                mMediaPlayer.start();
+            }
+        });
+
+
+        findViewById(R.id.answer_button).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                updateCurrentEndpoint();
+                if (canUseCallControl() && mCallDirection != DIRECTION_OUTGOING) {
+                    mVoipCall.mCallControl.answer(AUDIO_CALL, Runnable::run,
+                            Utils.getLoggableOutcomeReceiver("answer"));
+                    mAudioRecord.startRecording();
+                    mMediaPlayer.start();
+                }
+            }
+        });
+
+
+        findViewById(R.id.set_call_inactive_button).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (canUseCallControl()) {
+                    mVoipCall.mCallControl.setInactive(Runnable::run,
+                            Utils.getLoggableOutcomeReceiver("setInactive"));
+                }
+                mAudioRecord.stop();
+                mMediaPlayer.pause();
+            }
+        });
+
+        findViewById(R.id.disconnect_call_button).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                disconnectAndStopAudio();
+                finish();
+            }
+        });
+
+        findViewById(R.id.start_stream_button).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (canUseCallControl()) {
+                    mVoipCall.mCallControl.startCallStreaming(Runnable::run,
+                            Utils.getLoggableOutcomeReceiver("startCallStream"));
+                }
+            }
+        });
+
+        findViewById(R.id.request_earpiece).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (canUseCallControl() && mVoipCall.mEarpieceEndpoint != null) {
+                    requestEndpointChange(mVoipCall.mEarpieceEndpoint,
+                            "Request EARPIECE Endpoint:");
+                }
+            }
+        });
+
+        findViewById(R.id.request_speaker).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (canUseCallControl() && mVoipCall.mSpeakerEndpoint != null) {
+                    requestEndpointChange(mVoipCall.mSpeakerEndpoint,
+                            "Request SPEAKER Endpoint:");
+                }
+            }
+        });
+
+        findViewById(R.id.request_bluetooth).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if (canUseCallControl() && mVoipCall.mBluetoothEndpoint != null) {
+                    requestEndpointChange(mVoipCall.mBluetoothEndpoint,
+                            "Request BLUETOOTH Endpoint:");
+                }
+            }
+        });
+
+        findViewById(R.id.crash_app).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                // To test edge cases, it may be useful to crash the app. To do this, throwing a
+                // RuntimeException is sufficient.
+                throw new RuntimeException(
+                        "Intentionally throwing RuntimeException from InCallActivity");
+            }
+        });
+
+        findViewById(R.id.updateCallStyleNotification).setOnClickListener(
+                new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        Utils.updateCallStyleNotification_toOngoingCall(getApplicationContext());
+                    }
+                });
+    }
+
+    @Override
+    protected void onStop() {
+        Log.i(TAG, "onStop: InCallActivity has stopped");
+        super.onStop();
+    }
+
+    @Override
+    protected void onDestroy() {
+        Log.i(TAG, "onDestroy: InCallActivity has been destroyed");
+        disconnectAndStopAudio();
+        super.onDestroy();
+    }
+
+    private boolean canUseCallControl() {
+        return mVoipCall != null && mVoipCall.mCallControl != null;
+    }
+
+    private void updateCurrentEndpoint() {
+        if (mCurrentEndpointTextView != null) {
+            if (mVoipCall != null && mVoipCall.mCurrentEndpoint != null) {
+                mCurrentEndpointTextView.setText("CallEndpoint=[" +
+                        mVoipCall.mCurrentEndpoint.getEndpointName() + "]");
+            }
+        }
+    }
+
+    private void updateCurrentEndpointWithOnResult(CallEndpoint endpoint) {
+        if (mCurrentEndpointTextView != null) {
+            if (mVoipCall != null && mVoipCall.mCurrentEndpoint != null) {
+                mCurrentEndpointTextView.setText("CallEndpoint=[" +
+                        endpoint.getEndpointName() + "]");
+            }
+        }
+    }
+
+    private void updateCallId() {
+        TextView view = findViewById(R.id.getCallIdTextView);
+        StringBuilder sb = new StringBuilder();
+        sb.append("[");
+        if (canUseCallControl()) {
+            String id = mVoipCall.mCallControl.getCallId().toString();
+            sb.append(id);
+        } else {
+            sb.append("Error Getting Id");
+        }
+        sb.append("]");
+        try {
+            view.setText(sb.toString());
+        }
+        catch (Exception e){
+            // ignore updating the ui
+        }
+    }
+
+    private void addCall() {
+        mVoipCall = new MyVoipCall("123");
+
+        CallAttributes callAttributes =
+                new CallAttributes.Builder(
+                        Utils.PHONE_ACCOUNT_HANDLE,
+                        mCallDirection,
+                        "Alan Turing",
+                        Uri.parse("tel:+16506959001")).build();
+
+        mTelecomManager.addCall(callAttributes, Runnable::run,
+                new OutcomeReceiver<CallControl, CallException>() {
+                    @Override
+                    public void onResult(CallControl callControl) {
+                        Log.i(TAG, "addCall: onResult: callback fired");
+                        Utils.postIncomingCallStyleNotification(getApplicationContext());
+                        mVoipCall.onAddCallControl(callControl);
+                        updateCallId();
+                        updateCurrentEndpoint();
+                    }
+
+                    @Override
+                    public void onError(CallException exception) {
+
+                    }
+                },
+                mVoipCall, mVoipCall);
+    }
+
+    private void disconnectAndStopAudio() {
+        if (mVoipCall != null) {
+            mVoipCall.mCallControl.disconnect(
+                    new DisconnectCause(DisconnectCause.LOCAL),
+                    Runnable::run,
+                    Utils.getLoggableOutcomeReceiver("disconnect"));
+        }
+        mMediaPlayer.stop();
+        mAudioRecord.stop();
+        try {
+            mAudioRecord.unregisterAudioRecordingCallback(mAudioRecordingCallback);
+            Utils.clearNotification(getApplicationContext());
+        } catch (Exception e) {
+            // pass through
+        }
+    }
+
+    private void requestEndpointChange(CallEndpoint endpoint, String tag) {
+        mVoipCall.mCallControl.requestCallEndpointChange(
+                endpoint,
+                Runnable::run,
+                new OutcomeReceiver<Void, CallException>() {
+                    @Override
+                    public void onResult(Void result) {
+                        Log.i(TAG, String.format("requestEndpointChange: success w/ %s", tag));
+                        updateCurrentEndpointWithOnResult(endpoint);
+                    }
+
+                    @Override
+                    public void onError(CallException e) {
+                        Log.i(TAG, String.format("requestEndpointChange: %s failed to switch to "
+                                + "endpoint=[%s] due to exception=[%s]", tag, endpoint, e));
+                    }
+                });
+    }
+}
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..e2b5b14
--- /dev/null
+++ b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/MyVoipCall.java
@@ -0,0 +1,123 @@
+/*
+ * 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.ArrayList;
+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;
+    public CallControl mCallControl;
+    public CallEndpoint mCurrentEndpoint;
+    public CallEndpoint mEarpieceEndpoint;
+    public CallEndpoint mSpeakerEndpoint;
+    public CallEndpoint mBluetoothEndpoint;
+    List<CallEndpoint> mAvailableEndpoint = new ArrayList<>();
+
+    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));
+        mCurrentEndpoint = 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));
+            if (endpoint != null && endpoint.getEndpointType() == CallEndpoint.TYPE_EARPIECE) {
+                mEarpieceEndpoint = endpoint;
+            }
+            if (endpoint != null && endpoint.getEndpointType() == CallEndpoint.TYPE_SPEAKER) {
+                mSpeakerEndpoint = endpoint;
+            }
+            if (endpoint != null && endpoint.getEndpointType() == CallEndpoint.TYPE_BLUETOOTH) {
+                mBluetoothEndpoint = endpoint;
+            }
+        }
+        mAvailableEndpoint = availableEndpoints;
+    }
+
+    @Override
+    public void onMuteStateChanged(boolean isMuted) {
+    }
+}
diff --git a/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/Utils.java b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/Utils.java
new file mode 100644
index 0000000..ec448b2
--- /dev/null
+++ b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/Utils.java
@@ -0,0 +1,176 @@
+/*
+ * 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.transactionalVoipApp;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Person;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioRecord;
+import android.media.AudioRecordingConfiguration;
+import android.media.MediaPlayer;
+import android.media.MediaRecorder;
+import android.os.OutcomeReceiver;
+import android.telecom.CallException;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.util.Log;
+
+import java.util.List;
+
+public class Utils {
+    public static final String TAG = "TransactionalAppUtils";
+    public static final String CALLER_NAME = "Sundar Pichai";
+    public static final String sEXTRAS_KEY = "ExtrasKey";
+    public static final String sCALL_DIRECTION_KEY = "CallDirectionKey";
+    public static final String CHANNEL_ID = "TelecomVoipAppChannelId";
+    public static final int CALL_NOTIFICATION_ID = 123456;
+    private static final int SAMPLING_RATE_HZ = 44100;
+
+    public static final PhoneAccountHandle PHONE_ACCOUNT_HANDLE = new PhoneAccountHandle(
+            new ComponentName("com.android.server.telecom.transactionalVoipApp",
+                    "com.android.server.telecom.transactionalVoipApp.VoipAppMainActivity"), "123");
+
+    public static final PhoneAccount PHONE_ACCOUNT =
+            PhoneAccount.builder(PHONE_ACCOUNT_HANDLE, "test label")
+                    .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED |
+                            PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS).build();
+
+
+    public static Notification createCallStyleNotification(Context context) {
+        Intent answerIntent = new Intent(context, InCallActivity.class);
+        Intent rejectIntent = new Intent(context, InCallActivity.class);
+
+        // Creating a pending intent and wrapping our intent
+        PendingIntent pendingAnswer = PendingIntent.getActivity(context, 0,
+                answerIntent, PendingIntent.FLAG_IMMUTABLE);
+        PendingIntent pendingReject = PendingIntent.getActivity(context, 0,
+                rejectIntent, PendingIntent.FLAG_IMMUTABLE);
+
+
+        Notification callStyleNotification = new Notification.Builder(context,
+                CHANNEL_ID)
+                .setContentText("Answer/Reject call")
+                .setContentTitle("Incoming call")
+                .setSmallIcon(R.drawable.ic_android_black_24dp)
+                .setStyle(Notification.CallStyle.forIncomingCall(
+                        new Person.Builder().setName(CALLER_NAME).setImportant(true).build(),
+                        pendingAnswer, pendingReject)
+                )
+                .setFullScreenIntent(pendingAnswer, true)
+                .setOngoing(true)
+                .build();
+
+        return callStyleNotification;
+    }
+
+    public static void postIncomingCallStyleNotification(Context context) {
+        NotificationManager nm = context.getSystemService(NotificationManager.class);
+        nm.notify(Utils.CALL_NOTIFICATION_ID, createCallStyleNotification(context));
+    }
+
+    public static void updateCallStyleNotification_toOngoingCall(Context context) {
+        PendingIntent ongoingCall = PendingIntent.getActivity(context, 0,
+                new Intent(""), PendingIntent.FLAG_IMMUTABLE);
+
+        Notification callStyleNotification = new Notification.Builder(context,
+                CHANNEL_ID)
+                .setContentText("active call in the TransactionalTestApp")
+                .setContentTitle("Ongoing call")
+                .setSmallIcon(R.drawable.ic_android_black_24dp)
+                .setStyle(Notification.CallStyle.forOngoingCall(
+                        new Person.Builder().setName(CALLER_NAME).setImportant(true).build(),
+                        ongoingCall)
+                )
+                .setFullScreenIntent(ongoingCall, true)
+                .setOngoing(true)
+                .build();
+
+        NotificationManager notificationManager =
+                context.getSystemService(NotificationManager.class);
+
+        notificationManager.notify(CALL_NOTIFICATION_ID, callStyleNotification);
+    }
+
+    public static void clearNotification(Context context) {
+        NotificationManager notificationManager =
+                context.getSystemService(NotificationManager.class);
+        if (notificationManager != null) {
+            notificationManager.cancel(CALL_NOTIFICATION_ID);
+        }
+    }
+
+    public static MediaPlayer createMediaPlayer(Context context) {
+        int audioToPlay = (Math.random() > 0.5f) ?
+                com.android.server.telecom.transactionalVoipApp.R.raw.sample_audio :
+                com.android.server.telecom.transactionalVoipApp.R.raw.sample_audio2;
+        MediaPlayer mediaPlayer = MediaPlayer.create(context, audioToPlay);
+        mediaPlayer.setLooping(true);
+        return mediaPlayer;
+    }
+
+    public static AudioRecord createAudioRecord() {
+        return new AudioRecord.Builder()
+                .setAudioFormat(new AudioFormat.Builder()
+                        .setSampleRate(SAMPLING_RATE_HZ)
+                        .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                        .setChannelMask(AudioFormat.CHANNEL_IN_MONO).build())
+                .setAudioSource(MediaRecorder.AudioSource.DEFAULT)
+                .setBufferSizeInBytes(
+                        AudioRecord.getMinBufferSize(SAMPLING_RATE_HZ,
+                                AudioFormat.CHANNEL_IN_MONO,
+                                AudioFormat.ENCODING_PCM_16BIT) * 10)
+                .build();
+    }
+
+
+    public static AudioManager.AudioRecordingCallback getAudioRecordingCallback() {
+        return new AudioManager.AudioRecordingCallback() {
+            @Override
+            public void onRecordingConfigChanged(List<AudioRecordingConfiguration> configs) {
+                super.onRecordingConfigChanged(configs);
+
+                for (AudioRecordingConfiguration config : configs) {
+                    if (config != null) {
+                        Log.i(TAG, String.format("onRecordingConfigChanged: random: "
+                                        + "isClientSilenced=[%b], config=[%s]",
+                                config.isClientSilenced(), config));
+                    }
+                }
+            }
+        };
+    }
+
+    public static OutcomeReceiver<Void, CallException> getLoggableOutcomeReceiver(String tag) {
+        return new OutcomeReceiver<Void, CallException>() {
+            @Override
+            public void onResult(Void result) {
+                Log.i(TAG, tag + " : onResult");
+            }
+
+            @Override
+            public void onError(CallException exception) {
+                Log.i(TAG, tag + " : onError");
+            }
+        };
+    }
+}
\ No newline at end of file
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..72a3906
--- /dev/null
+++ b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/VoipAppMainActivity.java
@@ -0,0 +1,146 @@
+/*
+ * 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.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Intent;
+import android.media.AudioManager;
+import android.media.AudioRecord;
+import android.media.MediaPlayer;
+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.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 final String ACT_STATE_TAG = "VoipActivityState";
+    private static TelecomManager mTelecomManager;
+    NotificationManager mNotificationManager;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        Log.i(TAG, ACT_STATE_TAG + "onCreate");
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main_activity);
+
+        mTelecomManager = getSystemService(TelecomManager.class);
+        mNotificationManager = getSystemService(NotificationManager.class);
+        // create a notification channel
+        if (mNotificationManager != null) {
+            mNotificationManager.createNotificationChannel(new NotificationChannel(
+                    Utils.CHANNEL_ID, "new call channel",
+                    NotificationManager.IMPORTANCE_DEFAULT));
+        }
+
+        // register account
+        findViewById(R.id.registerButton).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                mTelecomManager.registerPhoneAccount(Utils.PHONE_ACCOUNT);
+            }
+        });
+
+        // Start a foreground service that will post a notification within 10 seconds.
+        // This is helpful for debugging scenarios where the app is in the background and posting
+        // an incoming call notification.
+        findViewById(R.id.startForegroundService).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Intent startForegroundService = new Intent(getApplicationContext(),
+                        BackgroundIncomingCallService.class);
+                getApplicationContext().startForegroundService(startForegroundService);
+            }
+        });
+
+
+        // post a new call notification and start an InCall activity
+        findViewById(R.id.startOutgoingCall).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                startInCallActivity(DIRECTION_OUTGOING);
+            }
+        });
+
+        // post a new call notification and start an InCall activity
+        findViewById(R.id.startIncomingCall).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                startInCallActivity(DIRECTION_INCOMING);
+            }
+        });
+
+    }
+
+    private void startInCallActivity(int direction) {
+        Bundle extras = new Bundle();
+        extras.putInt(Utils.sCALL_DIRECTION_KEY, direction);
+        Intent intent = new Intent(getApplicationContext(), InCallActivity.class);
+        intent.putExtra(Utils.sEXTRAS_KEY, extras);
+        startActivity(intent);
+    }
+
+    @Override
+    protected void onResume() {
+        Log.i(TAG, ACT_STATE_TAG + " onResume: When the activity enters the Resumed state,"
+                + " it comes to the foreground");
+        super.onResume();
+    }
+
+    @Override
+    protected void onPause() {
+        Log.i(TAG, ACT_STATE_TAG + " onPause: The system calls this method as the first"
+                + " indication that the user is leaving your activity.  It indicates that the"
+                + " activity is no longer in the foreground, but it is still visible if the user"
+                + " is in multi-window mode");
+        super.onPause();
+    }
+
+    @Override
+    protected void onStop() {
+        Log.i(TAG, ACT_STATE_TAG + "onStop: When your activity is no longer visible to"
+                + " the user, it enters the Stopped state,");
+        super.onStop();
+    }
+
+    @Override
+    protected void onRestart() {
+        Log.i(TAG, ACT_STATE_TAG + " onRestart: onStop has called onRestart and the "
+                + "activity comes back to interact with the user");
+        super.onRestart();
+    }
+
+    @Override
+    protected void onDestroy() {
+        Log.i(TAG, ACT_STATE_TAG + " onDestroy: is called before the activity is"
+                + " destroyed. ");
+        Utils.clearNotification(getApplicationContext());
+        super.onDestroy();
+    }
+}
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..bd81a2f 100644
--- a/tests/src/com/android/server/telecom/tests/BasicCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
@@ -16,8 +16,11 @@
 
 package com.android.server.telecom.tests;
 
+import static com.android.server.telecom.tests.ConnectionServiceFixture.STATUS_HINTS_EXTRA;
+
 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.mockito.ArgumentMatchers.nullable;
@@ -38,12 +41,17 @@
 import android.content.IContentProvider;
 import android.content.pm.PackageManager;
 import android.media.AudioDeviceInfo;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
 import android.media.AudioManager;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.Process;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.BlockedNumberContract;
 import android.telecom.Call;
 import android.telecom.CallAudioState;
@@ -54,12 +62,14 @@
 import android.telecom.ParcelableCall;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
+import android.telecom.StatusHints;
 import android.telecom.TelecomManager;
 import android.telecom.VideoProfile;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.test.suitebuilder.annotation.MediumTest;
 
 import androidx.test.filters.FlakyTest;
+import androidx.test.filters.SmallTest;
 
 import com.android.internal.telecom.IInCallAdapter;
 import android.telecom.CallerInfo;
@@ -87,6 +97,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";
 
@@ -194,7 +205,7 @@
     @Test
     public void testTelecomManagerAcceptRingingVideoCall() throws Exception {
         IdPair ids = startIncomingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
-                VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA);
+                VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA, null);
 
         assertEquals(Call.STATE_RINGING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
         assertEquals(Call.STATE_RINGING, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
@@ -223,7 +234,7 @@
     @Test
     public void testTelecomManagerAcceptRingingVideoCallAsAudio() throws Exception {
         IdPair ids = startIncomingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
-                VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA);
+                VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA, null);
 
         assertEquals(Call.STATE_RINGING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
         assertEquals(Call.STATE_RINGING, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
@@ -254,7 +265,7 @@
     @Test
     public void testTelecomManagerAcceptRingingInvalidVideoState() throws Exception {
         IdPair ids = startIncomingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
-                VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA);
+                VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA, null);
 
         assertEquals(Call.STATE_RINGING, mInCallServiceFixtureX.getCall(ids.mCallId).getState());
         assertEquals(Call.STATE_RINGING, mInCallServiceFixtureY.getCall(ids.mCallId).getState());
@@ -327,7 +338,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 +403,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 +453,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 +505,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);
@@ -692,13 +703,13 @@
     @MediumTest
     @Test
     public void testBasicConferenceCall() throws Exception {
-        makeConferenceCall();
+        makeConferenceCall(null, null);
     }
 
     @MediumTest
     @Test
     public void testAddCallToConference1() throws Exception {
-        ParcelableCall conferenceCall = makeConferenceCall();
+        ParcelableCall conferenceCall = makeConferenceCall(null, null);
         IdPair callId3 = startAndMakeActiveOutgoingCall("650-555-1214",
                 mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
         // testAddCallToConference{1,2} differ in the order of arguments to InCallAdapter#conference
@@ -716,7 +727,7 @@
     @MediumTest
     @Test
     public void testAddCallToConference2() throws Exception {
-        ParcelableCall conferenceCall = makeConferenceCall();
+        ParcelableCall conferenceCall = makeConferenceCall(null, null);
         IdPair callId3 = startAndMakeActiveOutgoingCall("650-555-1214",
                 mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
         mInCallServiceFixtureX.getInCallAdapter()
@@ -974,7 +985,7 @@
     public void testOutgoingCallSelectPhoneAccountVideo() throws Exception {
         startOutgoingPhoneCallPendingCreateConnection("650-555-1212",
                 null, mConnectionServiceFixtureA,
-                Process.myUserHandle(), VideoProfile.STATE_BIDIRECTIONAL);
+                Process.myUserHandle(), VideoProfile.STATE_BIDIRECTIONAL, null);
         com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
                 .iterator().next();
         assert(call.isVideoCallingSupportedByPhoneAccount());
@@ -997,7 +1008,7 @@
     public void testOutgoingCallSelectPhoneAccountNoVideo() throws Exception {
         startOutgoingPhoneCallPendingCreateConnection("650-555-1212",
                 null, mConnectionServiceFixtureA,
-                Process.myUserHandle(), VideoProfile.STATE_BIDIRECTIONAL);
+                Process.myUserHandle(), VideoProfile.STATE_BIDIRECTIONAL, null);
         com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
                 .iterator().next();
         assert(call.isVideoCallingSupportedByPhoneAccount());
@@ -1212,4 +1223,145 @@
         assertTrue(muteValues.get(0));
         assertFalse(muteValues.get(1));
     }
+
+    /**
+     * Verifies that StatusHints image is validated in ConnectionServiceWrapper#addConferenceCall
+     * when the image doesn't belong to the calling user. Simulates a scenario where an app
+     * could manipulate the contents of the bundle and send it via the binder to upload an image
+     * from another user.
+     *
+     * @throws Exception
+     */
+    @SmallTest
+    @Test
+    public void testValidateStatusHintsImage_addConferenceCall() throws Exception {
+        Intent callIntent1 = new Intent();
+        // Stub intent for call2
+        Intent callIntent2 = new Intent();
+        Bundle callExtras1 = new Bundle();
+        Icon icon = Icon.createWithContentUri("content://10@media/external/images/media/");
+        // Load StatusHints extra into TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS to be processed
+        // as the call extras. This will be leveraged in ConnectionServiceFixture to set the
+        // StatusHints for the given connection.
+        StatusHints statusHints = new StatusHints(icon);
+        assertNotNull(statusHints.getIcon());
+        callExtras1.putParcelable(STATUS_HINTS_EXTRA, statusHints);
+        callIntent1.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, callExtras1);
+
+        // Start conference call to invoke ConnectionServiceWrapper#addConferenceCall.
+        // Note that the calling user would be User 0.
+        ParcelableCall conferenceCall = makeConferenceCall(callIntent1, callIntent2);
+
+        // Ensure that StatusHints was set.
+        assertNotNull(mInCallServiceFixtureX.getCall(mInCallServiceFixtureX.mLatestCallId)
+                .getStatusHints());
+        // Ensure that the StatusHints image icon was disregarded.
+        assertNull(mInCallServiceFixtureX.getCall(mInCallServiceFixtureX.mLatestCallId)
+                .getStatusHints().getIcon());
+    }
+
+    /**
+     * Verifies that StatusHints image is validated in
+     * ConnectionServiceWrapper#handleCreateConnectionComplete when the image doesn't belong to the
+     * calling user. Simulates a scenario where an app could manipulate the contents of the
+     * bundle and send it via the binder to upload an image from another user.
+     *
+     * @throws Exception
+     */
+    @SmallTest
+    @Test
+    public void testValidateStatusHintsImage_handleCreateConnectionComplete() throws Exception {
+        Bundle extras = new Bundle();
+        Icon icon = Icon.createWithContentUri("content://10@media/external/images/media/");
+        // Load the bundle with the test extra in order to simulate an app directly invoking the
+        // binder on ConnectionServiceWrapper#handleCreateConnectionComplete.
+        StatusHints statusHints = new StatusHints(icon);
+        assertNotNull(statusHints.getIcon());
+        extras.putParcelable(STATUS_HINTS_EXTRA, statusHints);
+
+        // Start incoming call with StatusHints extras
+        // Note that the calling user in ConnectionServiceWrapper#handleCreateConnectionComplete
+        // would be User 0.
+        IdPair ids = startIncomingPhoneCallWithExtras("650-555-1212",
+                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA, extras);
+
+        // Ensure that StatusHints was set.
+        assertNotNull(mInCallServiceFixtureX.getCall(ids.mCallId).getStatusHints());
+        // Ensure that the StatusHints image icon was disregarded.
+        assertNull(mInCallServiceFixtureX.getCall(ids.mCallId).getStatusHints().getIcon());
+    }
+
+    /**
+     * Verifies that StatusHints image is validated in ConnectionServiceWrapper#setStatusHints
+     * when the image doesn't belong to the calling user. Simulates a scenario where an app
+     * could manipulate the contents of the bundle and send it via the binder to upload an image
+     * from another user.
+     *
+     * @throws Exception
+     */
+    @SmallTest
+    @Test
+    public void testValidateStatusHintsImage_setStatusHints() throws Exception {
+        IdPair outgoing = startAndMakeActiveOutgoingCall("650-555-1214",
+                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
+
+        // Modify existing connection with StatusHints image exploit
+        Icon icon = Icon.createWithContentUri("content://10@media/external/images/media/");
+        StatusHints statusHints = new StatusHints(icon);
+        assertNotNull(statusHints.getIcon());
+        ConnectionServiceFixture.ConnectionInfo connectionInfo = mConnectionServiceFixtureA
+                .mConnectionById.get(outgoing.mConnectionId);
+        connectionInfo.statusHints = statusHints;
+
+        // Invoke ConnectionServiceWrapper#setStatusHints.
+        // Note that the calling user would be User 0.
+        mConnectionServiceFixtureA.sendSetStatusHints(outgoing.mConnectionId);
+        waitForHandlerAction(mConnectionServiceFixtureA.mConnectionServiceDelegate.getHandler(),
+                TEST_TIMEOUT);
+
+        // Ensure that StatusHints was set.
+        assertNotNull(mInCallServiceFixtureX.getCall(outgoing.mCallId).getStatusHints());
+        // Ensure that the StatusHints image icon was disregarded.
+        assertNull(mInCallServiceFixtureX.getCall(outgoing.mCallId)
+                .getStatusHints().getIcon());
+    }
+
+    /**
+     * Verifies that StatusHints image is validated in
+     * ConnectionServiceWrapper#addExistingConnection when the image doesn't belong to the calling
+     * user. Simulates a scenario where an app could manipulate the contents of the bundle and
+     * send it via the binder to upload an image from another user.
+     *
+     * @throws Exception
+     */
+    @SmallTest
+    @Test
+    public void testValidateStatusHintsImage_addExistingConnection() throws Exception {
+        IdPair outgoing = startAndMakeActiveOutgoingCall("650-555-1214",
+                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
+        Connection existingConnection = mConnectionServiceFixtureA.mLatestConnection;
+
+        // Modify existing connection with StatusHints image exploit
+        Icon icon = Icon.createWithContentUri("content://10@media/external/images/media/");
+        StatusHints modifiedStatusHints = new StatusHints(icon);
+        assertNotNull(modifiedStatusHints.getIcon());
+        ConnectionServiceFixture.ConnectionInfo connectionInfo = mConnectionServiceFixtureA
+                .mConnectionById.get(outgoing.mConnectionId);
+        connectionInfo.statusHints = modifiedStatusHints;
+
+        // Invoke ConnectionServiceWrapper#addExistingConnection.
+        // Note that the calling user would be User 0.
+        mConnectionServiceFixtureA.sendAddExistingConnection(outgoing.mConnectionId);
+        waitForHandlerAction(mConnectionServiceFixtureA.mConnectionServiceDelegate.getHandler(),
+                TEST_TIMEOUT);
+
+        // Ensure that StatusHints was set. Due to test setup, the ParcelableConnection object that
+        // is passed into sendAddExistingConnection is instantiated on invocation. The call's
+        // StatusHints are not updated at the time of completion, so instead, we can verify that
+        // the ParcelableConnection object was modified.
+        assertNotNull(mConnectionServiceFixtureA.mLatestParcelableConnection.getStatusHints());
+        // Ensure that the StatusHints image icon was disregarded.
+        assertNull(mConnectionServiceFixtureA.mLatestParcelableConnection
+                .getStatusHints().getIcon());
+    }
 }
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..d7854a5 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(true)
+                .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_COMMUNICATION_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/CallExtrasTest.java b/tests/src/com/android/server/telecom/tests/CallExtrasTest.java
index 926d740..cf44cfe 100644
--- a/tests/src/com/android/server/telecom/tests/CallExtrasTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallExtrasTest.java
@@ -370,7 +370,7 @@
     @LargeTest
     @Test
     public void testConferenceSetExtras() throws Exception {
-        ParcelableCall call = makeConferenceCall();
+        ParcelableCall call = makeConferenceCall(null, null);
         String conferenceId = call.getId();
 
         Conference conference = mConnectionServiceFixtureA.mLatestConference;
@@ -414,7 +414,7 @@
     @FlakyTest(bugId = 117751305)
     @Test
     public void testConferenceExtraOperations() throws Exception {
-        ParcelableCall call = makeConferenceCall();
+        ParcelableCall call = makeConferenceCall(null, null);
         String conferenceId = call.getId();
         Conference conference = mConnectionServiceFixtureA.mLatestConference;
         assertNotNull(conference);
@@ -450,7 +450,7 @@
     @LargeTest
     @Test
     public void testConferenceICS() throws Exception {
-        ParcelableCall call = makeConferenceCall();
+        ParcelableCall call = makeConferenceCall(null, null);
         String conferenceId = call.getId();
         Conference conference = mConnectionServiceFixtureA.mLatestConference;
 
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/CallLogManagerTest.java b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
index b9f5667..9466220 100644
--- a/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallLogManagerTest.java
@@ -1062,7 +1062,7 @@
         when(fakeCall.getVideoStateHistory()).thenReturn(callVideoState);
         when(fakeCall.getPostDialDigits()).thenReturn(postDialDigits);
         when(fakeCall.getViaNumber()).thenReturn(viaNumber);
-        when(fakeCall.getInitiatingUser()).thenReturn(initiatingUser);
+        when(fakeCall.getAssociatedUser()).thenReturn(initiatingUser);
         when(fakeCall.getCallDataUsage()).thenReturn(callDataUsage);
         when(fakeCall.isEmergencyCall()).thenReturn(
                 phoneAccountHandle.equals(EMERGENCY_ACCT_HANDLE));
diff --git a/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java b/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java
index f2fe045..01446d1 100644
--- a/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallRedirectionProcessorTest.java
@@ -153,8 +153,9 @@
     }
 
     private void enableUserDefinedCallRedirectionService() {
-        when(mCallRedirectionProcessorHelper.getUserDefinedCallRedirectionService()).thenReturn(
-                USER_DEFINED_SERVICE_TEST_COMPONENT_NAME);
+        when(mCallRedirectionProcessorHelper
+                .getUserDefinedCallRedirectionService(any(UserHandle.class)))
+                .thenReturn(USER_DEFINED_SERVICE_TEST_COMPONENT_NAME);
     }
 
     private void enableCarrierCallRedirectionService() {
@@ -163,8 +164,9 @@
     }
 
     private void disableUserDefinedCallRedirectionService() {
-        when(mCallRedirectionProcessorHelper.getUserDefinedCallRedirectionService()).thenReturn(
-                null);
+        when(mCallRedirectionProcessorHelper
+                .getUserDefinedCallRedirectionService(any(UserHandle.class)))
+                .thenReturn(null);
     }
 
     private void disableCarrierCallRedirectionService() {
@@ -193,9 +195,9 @@
         startProcessWithNoGateWayInfo();
         disableUserDefinedCallRedirectionService();
         disableCarrierCallRedirectionService();
-        mProcessor.performCallRedirection();
+        mProcessor.performCallRedirection(UserHandle.CURRENT);
         verify(mContext, times(0)).bindServiceAsUser(any(Intent.class),
-                any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+                any(ServiceConnection.class), anyInt(), eq(UserHandle.CURRENT));
         verify(mCallsManager, times(1)).onCallRedirectionComplete(eq(mCall), eq(mHandle),
                 eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
                 eq(false), eq(CallRedirectionProcessor.UI_TYPE_NO_ACTION));
@@ -208,9 +210,9 @@
         waitForHandlerAction(mProcessor.getHandler(), HANDLER_TIMEOUT_DELAY);
         disableUserDefinedCallRedirectionService();
         enableCarrierCallRedirectionService();
-        mProcessor.performCallRedirection();
+        mProcessor.performCallRedirection(UserHandle.CURRENT);
         verify(mContext, times(1)).bindServiceAsUser(any(Intent.class),
-                any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+                any(ServiceConnection.class), anyInt(), eq(UserHandle.CURRENT));
         verify(mCallsManager, times(0)).onCallRedirectionComplete(eq(mCall), any(),
                 eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
                 eq(false), eq(CallRedirectionProcessor.UI_TYPE_NO_ACTION));
@@ -228,9 +230,9 @@
         waitForHandlerAction(mProcessor.getHandler(), HANDLER_TIMEOUT_DELAY);
         enableUserDefinedCallRedirectionService();
         disableCarrierCallRedirectionService();
-        mProcessor.performCallRedirection();
+        mProcessor.performCallRedirection(UserHandle.CURRENT);
         verify(mContext, times(1)).bindServiceAsUser(any(Intent.class),
-                any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+                any(ServiceConnection.class), anyInt(), eq(UserHandle.CURRENT));
         verify(mCallsManager, times(0)).onCallRedirectionComplete(eq(mCall), any(),
                 eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
                 eq(false), eq(CallRedirectionProcessor.UI_TYPE_USER_DEFINED_TIMEOUT));
@@ -256,10 +258,10 @@
         waitForHandlerAction(mProcessor.getHandler(), HANDLER_TIMEOUT_DELAY);
         enableUserDefinedCallRedirectionService();
         enableCarrierCallRedirectionService();
-        mProcessor.performCallRedirection();
+        mProcessor.performCallRedirection(UserHandle.CURRENT);
 
         verify(mContext, times(1)).bindServiceAsUser(any(Intent.class),
-                any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+                any(ServiceConnection.class), anyInt(), eq(UserHandle.CURRENT));
         verify(mCallsManager, times(0)).onCallRedirectionComplete(eq(mCall), any(),
                 eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
                 eq(false), eq(CallRedirectionProcessor.UI_TYPE_USER_DEFINED_TIMEOUT));
@@ -294,9 +296,9 @@
         startProcessWithGateWayInfo();
         enableUserDefinedCallRedirectionService();
         enableCarrierCallRedirectionService();
-        mProcessor.performCallRedirection();
+        mProcessor.performCallRedirection(UserHandle.CURRENT);
         verify(mContext, times(1)).bindServiceAsUser(any(Intent.class),
-                any(ServiceConnection.class), anyInt(), any(UserHandle.class));
+                any(ServiceConnection.class), anyInt(), eq(UserHandle.CURRENT));
         verify(mCallsManager, times(0)).onCallRedirectionComplete(eq(mCall), any(),
                 eq(mPhoneAccountHandle), eq(null), eq(SPEAKER_PHONE_ON), eq(VIDEO_STATE),
                 eq(false), eq(CallRedirectionProcessor.UI_TYPE_NO_ACTION));
@@ -313,13 +315,13 @@
         enableUserDefinedCallRedirectionService();
         disableCarrierCallRedirectionService();
 
-        mProcessor.performCallRedirection();
+        mProcessor.performCallRedirection(UserHandle.CURRENT);
 
         // Capture binding and mock it out.
         ArgumentCaptor<ServiceConnection> serviceConnectionCaptor = ArgumentCaptor.forClass(
                 ServiceConnection.class);
         verify(mContext, times(1)).bindServiceAsUser(any(Intent.class),
-                serviceConnectionCaptor.capture(), anyInt(), any(UserHandle.class));
+                serviceConnectionCaptor.capture(), anyInt(), eq(UserHandle.CURRENT));
 
         // Mock out a service which performed a redirection
         IBinder mockBinder = mock(IBinder.class);
@@ -361,7 +363,7 @@
         enableUserDefinedCallRedirectionService();
         disableCarrierCallRedirectionService();
 
-        mProcessor.performCallRedirection();
+        mProcessor.performCallRedirection(UserHandle.CURRENT);
 
         // Capture the binder
         ArgumentCaptor<ServiceConnection> serviceConnectionCaptor = ArgumentCaptor.forClass(
diff --git a/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java b/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java
index 09ba47e..4d8d497 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.getAssociatedUser()).
+                thenReturn(PA_HANDLE.getUserHandle());
         when(mContext.getPackageManager()).thenReturn(mPackageManager);
         when(mContext.getSystemService(TelecomManager.class))
                 .thenReturn(mTelecomManager);
@@ -132,7 +139,7 @@
         when(mParcelableCallUtilsConverter.toParcelableCall(
                 eq(mCall), anyBoolean(), eq(mPhoneAccountRegistrar))).thenReturn(null);
         when(mContext.bindServiceAsUser(nullable(Intent.class), nullable(ServiceConnection.class),
-                anyInt(), eq(UserHandle.CURRENT))).thenReturn(true);
+                anyInt(), eq(PA_HANDLE.getUserHandle()))).thenReturn(true);
         when(mPackageManager.queryIntentServicesAsUser(nullable(Intent.class), anyInt(), anyInt()))
                 .thenReturn(Collections.singletonList(mResolveInfo));
         doReturn(mCallScreeningService).when(mBinder).queryLocalInterface(anyString());
@@ -154,7 +161,7 @@
     @Test
     public void testContextFailToBind() throws Exception {
         when(mContext.bindServiceAsUser(nullable(Intent.class), nullable(ServiceConnection.class),
-                anyInt(), eq(UserHandle.CURRENT))).thenReturn(false);
+                anyInt(), eq(PA_HANDLE.getUserHandle()))).thenReturn(false);
         CallScreeningServiceFilter filter = new CallScreeningServiceFilter(mCall, PKG_NAME,
                 CallScreeningServiceFilter.PACKAGE_TYPE_CARRIER, mContext, mCallsManager,
                 mAppLabelProxy, mParcelableCallUtilsConverter);
@@ -199,7 +206,7 @@
         when(mPackageManager.checkPermission(Manifest.permission.READ_CONTACTS, PKG_NAME))
                 .thenReturn(PackageManager.PERMISSION_DENIED);
         when(mContext.bindServiceAsUser(nullable(Intent.class), nullable(ServiceConnection.class),
-                anyInt(), eq(UserHandle.CURRENT))).thenThrow(new SecurityException());
+                anyInt(), eq(PA_HANDLE.getUserHandle()))).thenThrow(new SecurityException());
         inputResult.contactExists = true;
         CallScreeningServiceFilter filter = new CallScreeningServiceFilter(mCall, PKG_NAME,
                 CallScreeningServiceFilter.PACKAGE_TYPE_USER_CHOSEN, mContext, mCallsManager,
@@ -397,7 +404,7 @@
                 .bindServiceAsUser(intentCaptor.capture(), serviceCaptor.capture(),
                 eq(Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
                         | Context.BIND_SCHEDULE_LIKE_TOP_APP),
-                eq(UserHandle.CURRENT));
+                eq(PA_HANDLE.getUserHandle()));
 
         Intent capturedIntent = intentCaptor.getValue();
         assertEquals(CallScreeningService.SERVICE_INTERFACE, capturedIntent.getAction());
diff --git a/tests/src/com/android/server/telecom/tests/CallTest.java b/tests/src/com/android/server/telecom/tests/CallTest.java
index d326a29..997e7dd 100644
--- a/tests/src/com/android/server/telecom/tests/CallTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallTest.java
@@ -18,51 +18,66 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
 
 import android.content.ComponentName;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.drawable.ColorDrawable;
 import android.net.Uri;
+import android.os.Bundle;
+import android.telecom.CallAttributes;
+import android.telecom.CallerInfo;
 import android.telecom.Connection;
 import android.telecom.DisconnectCause;
+import android.telecom.ParcelableConference;
+import android.telecom.ParcelableConnection;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
+import android.telecom.StatusHints;
+import android.telecom.TelecomManager;
+import android.telecom.VideoProfile;
+import android.telephony.CallQuality;
 import android.test.suitebuilder.annotation.SmallTest;
-import android.util.Log;
 import android.widget.Toast;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
 import com.android.server.telecom.Call;
+import com.android.server.telecom.CallIdMapper;
 import com.android.server.telecom.CallState;
 import com.android.server.telecom.CallerInfoLookupHelper;
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.ClockProxy;
 import com.android.server.telecom.ConnectionServiceWrapper;
-import com.android.server.telecom.InCallController;
-import com.android.server.telecom.InCallController.InCallServiceInfo;
 import com.android.server.telecom.PhoneAccountRegistrar;
 import com.android.server.telecom.PhoneNumberUtilsAdapter;
 import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.TransactionalServiceWrapper;
 import com.android.server.telecom.ui.ToastFactory;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 
+import java.util.Collections;
+
 @RunWith(AndroidJUnit4.class)
 public class CallTest extends TelecomTestCase {
     private static final Uri TEST_ADDRESS = Uri.parse("tel:555-1212");
@@ -87,6 +102,7 @@
     @Mock private Toast mMockToast;
     @Mock private PhoneNumberUtilsAdapter mMockPhoneNumberUtilsAdapter;
     @Mock private ConnectionServiceWrapper mMockConnectionService;
+    @Mock private TransactionalServiceWrapper mMockTransactionalService;
 
     private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
 
@@ -95,6 +111,7 @@
         super.setUp();
         doReturn(mMockCallerInfoLookupHelper).when(mMockCallsManager).getCallerInfoLookupHelper();
         doReturn(mMockPhoneAccountRegistrar).when(mMockCallsManager).getPhoneAccountRegistrar();
+        doReturn(0L).when(mMockClockProxy).elapsedRealtime();
         doReturn(SIM_1_ACCOUNT).when(mMockPhoneAccountRegistrar).getPhoneAccountUnchecked(
                 eq(SIM_1_HANDLE));
         doReturn(new ComponentName(mContext, CallTest.class))
@@ -110,23 +127,7 @@
     @Test
     @SmallTest
     public void testSetHasGoneActive() {
-        Call call = new Call(
-                "1", /* callId */
-                mContext,
-                mMockCallsManager,
-                mLock,
-                null /* ConnectionServiceRepository */,
-                mMockPhoneNumberUtilsAdapter,
-                TEST_ADDRESS,
-                null /* GatewayInfo */,
-                null /* connectionManagerPhoneAccountHandle */,
-                SIM_1_HANDLE,
-                Call.CALL_DIRECTION_INCOMING,
-                false /* shouldAttachToExistingConnection*/,
-                false /* isConference */,
-                mMockClockProxy,
-                mMockToastProxy);
-
+        Call call = createCall("1", Call.CALL_DIRECTION_INCOMING);
         assertFalse(call.hasGoneActiveBefore());
         call.setState(CallState.ACTIVE, "");
         assertTrue(call.hasGoneActiveBefore());
@@ -134,9 +135,56 @@
         assertTrue(call.hasGoneActiveBefore());
     }
 
+    /**
+     * Basic tests to check which call states are considered transitory.
+     */
     @Test
     @SmallTest
-    public void testDisconnectCauseWhenAudioProcessing() {
+    public void testIsCallStateTransitory() {
+        assertTrue(CallState.isTransitoryState(CallState.NEW));
+        assertTrue(CallState.isTransitoryState(CallState.CONNECTING));
+        assertTrue(CallState.isTransitoryState(CallState.DISCONNECTING));
+        assertTrue(CallState.isTransitoryState(CallState.ANSWERED));
+
+        assertFalse(CallState.isTransitoryState(CallState.SELECT_PHONE_ACCOUNT));
+        assertFalse(CallState.isTransitoryState(CallState.DIALING));
+        assertFalse(CallState.isTransitoryState(CallState.RINGING));
+        assertFalse(CallState.isTransitoryState(CallState.ACTIVE));
+        assertFalse(CallState.isTransitoryState(CallState.ON_HOLD));
+        assertFalse(CallState.isTransitoryState(CallState.DISCONNECTED));
+        assertFalse(CallState.isTransitoryState(CallState.ABORTED));
+        assertFalse(CallState.isTransitoryState(CallState.PULLING));
+        assertFalse(CallState.isTransitoryState(CallState.AUDIO_PROCESSING));
+        assertFalse(CallState.isTransitoryState(CallState.SIMULATED_RINGING));
+    }
+
+    /**
+     * Basic tests to check which call states are considered intermediate.
+     */
+    @Test
+    @SmallTest
+    public void testIsCallStateIntermediate() {
+        assertTrue(CallState.isIntermediateState(CallState.DIALING));
+        assertTrue(CallState.isIntermediateState(CallState.RINGING));
+
+        assertFalse(CallState.isIntermediateState(CallState.NEW));
+        assertFalse(CallState.isIntermediateState(CallState.CONNECTING));
+        assertFalse(CallState.isIntermediateState(CallState.DISCONNECTING));
+        assertFalse(CallState.isIntermediateState(CallState.ANSWERED));
+        assertFalse(CallState.isIntermediateState(CallState.SELECT_PHONE_ACCOUNT));
+        assertFalse(CallState.isIntermediateState(CallState.ACTIVE));
+        assertFalse(CallState.isIntermediateState(CallState.ON_HOLD));
+        assertFalse(CallState.isIntermediateState(CallState.DISCONNECTED));
+        assertFalse(CallState.isIntermediateState(CallState.ABORTED));
+        assertFalse(CallState.isIntermediateState(CallState.PULLING));
+        assertFalse(CallState.isIntermediateState(CallState.AUDIO_PROCESSING));
+        assertFalse(CallState.isIntermediateState(CallState.SIMULATED_RINGING));
+    }
+
+    @SmallTest
+    @Test
+    public void testIsCreateConnectionComplete() {
+        // A new call with basic info.
         Call call = new Call(
                 "1", /* callId */
                 mContext,
@@ -153,6 +201,22 @@
                 false /* isConference */,
                 mMockClockProxy,
                 mMockToastProxy);
+
+        // To start with connection creation isn't complete.
+        assertFalse(call.isCreateConnectionComplete());
+
+        // Need the bare minimum to get connection creation to complete.
+        ParcelableConnection connection = new ParcelableConnection(null, 0, 0, 0, 0, null, 0, null,
+                0, null, 0, false, false, 0L, 0L, null, null, Collections.emptyList(), null, null,
+                0, 0);
+        call.handleCreateConnectionSuccess(Mockito.mock(CallIdMapper.class), connection);
+        assertTrue(call.isCreateConnectionComplete());
+    }
+
+    @Test
+    @SmallTest
+    public void testDisconnectCauseWhenAudioProcessing() {
+        Call call = createCall("1", Call.CALL_DIRECTION_INCOMING);
         call.setState(CallState.AUDIO_PROCESSING, "");
         call.disconnect();
         call.setDisconnectCause(new DisconnectCause(DisconnectCause.LOCAL));
@@ -162,22 +226,7 @@
     @Test
     @SmallTest
     public void testDisconnectCauseWhenAudioProcessingAfterActive() {
-        Call call = new Call(
-                "1", /* callId */
-                mContext,
-                mMockCallsManager,
-                mLock,
-                null /* ConnectionServiceRepository */,
-                mMockPhoneNumberUtilsAdapter,
-                TEST_ADDRESS,
-                null /* GatewayInfo */,
-                null /* connectionManagerPhoneAccountHandle */,
-                SIM_1_HANDLE,
-                Call.CALL_DIRECTION_INCOMING,
-                false /* shouldAttachToExistingConnection*/,
-                false /* isConference */,
-                mMockClockProxy,
-                mMockToastProxy);
+        Call call = createCall("1", Call.CALL_DIRECTION_INCOMING);
         call.setState(CallState.AUDIO_PROCESSING, "");
         call.setState(CallState.ACTIVE, "");
         call.setState(CallState.AUDIO_PROCESSING, "");
@@ -189,22 +238,7 @@
     @Test
     @SmallTest
     public void testDisconnectCauseWhenSimulatedRingingAndDisconnect() {
-        Call call = new Call(
-                "1", /* callId */
-                mContext,
-                mMockCallsManager,
-                mLock,
-                null /* ConnectionServiceRepository */,
-                mMockPhoneNumberUtilsAdapter,
-                TEST_ADDRESS,
-                null /* GatewayInfo */,
-                null /* connectionManagerPhoneAccountHandle */,
-                SIM_1_HANDLE,
-                Call.CALL_DIRECTION_INCOMING,
-                false /* shouldAttachToExistingConnection*/,
-                false /* isConference */,
-                mMockClockProxy,
-                mMockToastProxy);
+        Call call = createCall("1", Call.CALL_DIRECTION_INCOMING);
         call.setState(CallState.SIMULATED_RINGING, "");
         call.disconnect();
         call.setDisconnectCause(new DisconnectCause(DisconnectCause.LOCAL));
@@ -214,22 +248,7 @@
     @Test
     @SmallTest
     public void testDisconnectCauseWhenSimulatedRingingAndReject() {
-        Call call = new Call(
-                "1", /* callId */
-                mContext,
-                mMockCallsManager,
-                mLock,
-                null /* ConnectionServiceRepository */,
-                mMockPhoneNumberUtilsAdapter,
-                TEST_ADDRESS,
-                null /* GatewayInfo */,
-                null /* connectionManagerPhoneAccountHandle */,
-                SIM_1_HANDLE,
-                Call.CALL_DIRECTION_INCOMING,
-                false /* shouldAttachToExistingConnection*/,
-                false /* isConference */,
-                mMockClockProxy,
-                mMockToastProxy);
+        Call call = createCall("1", Call.CALL_DIRECTION_INCOMING);
         call.setState(CallState.SIMULATED_RINGING, "");
         call.reject(false, "");
         call.setDisconnectCause(new DisconnectCause(DisconnectCause.LOCAL));
@@ -239,22 +258,7 @@
     @Test
     @SmallTest
     public void testCanPullCallRemovedDuringEmergencyCall() {
-        Call call = new Call(
-                "1", /* callId */
-                mContext,
-                mMockCallsManager,
-                mLock,
-                null /* ConnectionServiceRepository */,
-                mMockPhoneNumberUtilsAdapter,
-                TEST_ADDRESS,
-                null /* GatewayInfo */,
-                null /* connectionManagerPhoneAccountHandle */,
-                SIM_1_HANDLE,
-                Call.CALL_DIRECTION_INCOMING,
-                false /* shouldAttachToExistingConnection*/,
-                false /* isConference */,
-                mMockClockProxy,
-                mMockToastProxy);
+        Call call = createCall("1", Call.CALL_DIRECTION_INCOMING);
         boolean[] hasCalledConnectionCapabilitiesChanged = new boolean[1];
         call.addListener(new Call.ListenerBase() {
             @Override
@@ -287,22 +291,7 @@
     @Test
     @SmallTest
     public void testCanNotPullCallDuringEmergencyCall() {
-        Call call = new Call(
-                "1", /* callId */
-                mContext,
-                mMockCallsManager,
-                mLock,
-                null /* ConnectionServiceRepository */,
-                mMockPhoneNumberUtilsAdapter,
-                TEST_ADDRESS,
-                null /* GatewayInfo */,
-                null /* connectionManagerPhoneAccountHandle */,
-                SIM_1_HANDLE,
-                Call.CALL_DIRECTION_INCOMING,
-                false /* shouldAttachToExistingConnection*/,
-                false /* isConference */,
-                mMockClockProxy,
-                mMockToastProxy);
+        Call call = createCall("1", Call.CALL_DIRECTION_INCOMING);
         call.setConnectionService(mMockConnectionService);
         call.setConnectionProperties(Connection.PROPERTY_IS_EXTERNAL_CALL);
         call.setConnectionCapabilities(Connection.CAPABILITY_CAN_PULL_CALL);
@@ -318,6 +307,22 @@
     @Test
     @SmallTest
     public void testCallDirection() {
+        Call call = createCall("1");
+        boolean[] hasCallDirectionChanged = new boolean[1];
+        call.addListener(new Call.ListenerBase() {
+            @Override
+            public void onCallDirectionChanged(Call call) {
+                hasCallDirectionChanged[0] = true;
+            }
+        });
+        assertFalse(call.isIncoming());
+        call.setCallDirection(Call.CALL_DIRECTION_INCOMING);
+        assertTrue(hasCallDirectionChanged[0]);
+        assertTrue(call.isIncoming());
+    }
+
+    @Test
+    public void testIsSuppressedByDoNotDisturbExtra() {
         Call call = new Call(
                 "1", /* callId */
                 mContext,
@@ -334,16 +339,415 @@
                 true /* isConference */,
                 mMockClockProxy,
                 mMockToastProxy);
-        boolean[] hasCallDirectionChanged = new boolean[1];
-        call.addListener(new Call.ListenerBase() {
-            @Override
-            public void onCallDirectionChanged(Call call) {
-                hasCallDirectionChanged[0] = true;
-            }
-        });
-        assertFalse(call.isIncoming());
-        call.setCallDirection(Call.CALL_DIRECTION_INCOMING);
-        assertTrue(hasCallDirectionChanged[0]);
-        assertTrue(call.isIncoming());
+
+        assertFalse(call.wasDndCheckComputedForCall());
+        assertFalse(call.isCallSuppressedByDoNotDisturb());
+        call.setCallIsSuppressedByDoNotDisturb(true);
+        assertTrue(call.wasDndCheckComputedForCall());
+        assertTrue(call.isCallSuppressedByDoNotDisturb());
+    }
+
+    @Test
+    public void testGetConnectionServiceWrapper() {
+        Call call = new Call(
+                "1", /* callId */
+                mContext,
+                mMockCallsManager,
+                mLock,
+                null /* ConnectionServiceRepository */,
+                mMockPhoneNumberUtilsAdapter,
+                TEST_ADDRESS,
+                null /* GatewayInfo */,
+                null /* connectionManagerPhoneAccountHandle */,
+                SIM_1_HANDLE,
+                Call.CALL_DIRECTION_UNDEFINED,
+                false /* shouldAttachToExistingConnection*/,
+                true /* isConference */,
+                mMockClockProxy,
+                mMockToastProxy);
+
+        assertNull(call.getConnectionServiceWrapper());
+        assertFalse(call.isTransactionalCall());
+        call.setConnectionService(mMockConnectionService);
+        assertEquals(mMockConnectionService, call.getConnectionServiceWrapper());
+        call.setIsTransactionalCall(true);
+        assertTrue(call.isTransactionalCall());
+        assertNull(call.getConnectionServiceWrapper());
+        call.setTransactionServiceWrapper(mMockTransactionalService);
+        assertEquals(mMockTransactionalService, call.getTransactionServiceWrapper());
+    }
+
+    @Test
+    public void testCallEventCallbacksWereCalled() {
+        Call call = new Call(
+                "1", /* callId */
+                mContext,
+                mMockCallsManager,
+                mLock,
+                null /* ConnectionServiceRepository */,
+                mMockPhoneNumberUtilsAdapter,
+                TEST_ADDRESS,
+                null /* GatewayInfo */,
+                null /* connectionManagerPhoneAccountHandle */,
+                SIM_1_HANDLE,
+                Call.CALL_DIRECTION_UNDEFINED,
+                false /* shouldAttachToExistingConnection*/,
+                true /* isConference */,
+                mMockClockProxy,
+                mMockToastProxy);
+
+        // setup
+        call.setIsTransactionalCall(true);
+        assertTrue(call.isTransactionalCall());
+        assertNull(call.getConnectionServiceWrapper());
+        call.setTransactionServiceWrapper(mMockTransactionalService);
+        assertEquals(mMockTransactionalService, call.getTransactionServiceWrapper());
+
+        // assert CallEventCallback#onSetInactive is called
+        call.setState(CallState.ACTIVE, "test");
+        call.hold();
+        verify(mMockTransactionalService, times(1)).onSetInactive(call);
+
+        // assert CallEventCallback#onSetActive is called
+        call.setState(CallState.ON_HOLD, "test");
+        call.unhold();
+        verify(mMockTransactionalService, times(1)).onSetActive(call);
+
+        // assert CallEventCallback#onAnswer is called
+        call.setState(CallState.RINGING, "test");
+        call.answer(0);
+        verify(mMockTransactionalService, times(1)).onAnswer(call, 0);
+
+        // assert CallEventCallback#onDisconnect is called
+        call.setState(CallState.ACTIVE, "test");
+        call.disconnect();
+        verify(mMockTransactionalService, times(1)).onDisconnect(call,
+                call.getDisconnectCause());
+    }
+
+    @Test
+    @SmallTest
+    public void testSetConnectionPropertiesRttOnOff() {
+        Call call = createCall("1");
+        call.setConnectionService(mMockConnectionService);
+
+        call.setConnectionProperties(Connection.PROPERTY_IS_RTT);
+        verify(mMockCallsManager).playRttUpgradeToneForCall(any());
+        assertNotNull(null, call.getInCallToCsRttPipeForCs());
+        assertNotNull(null, call.getCsToInCallRttPipeForInCall());
+
+        call.setConnectionProperties(0);
+        assertNull(null, call.getInCallToCsRttPipeForCs());
+        assertNull(null, call.getCsToInCallRttPipeForInCall());
+    }
+
+    @Test
+    @SmallTest
+    public void testGetFromCallerInfo() {
+        Call call = createCall("1");
+
+        CallerInfo info = new CallerInfo();
+        info.setName("name");
+        info.setPhoneNumber("number");
+        info.cachedPhoto = new ColorDrawable();
+        info.cachedPhotoIcon = Bitmap.createBitmap(24, 24, Bitmap.Config.ALPHA_8);
+
+        ArgumentCaptor<CallerInfoLookupHelper.OnQueryCompleteListener> listenerCaptor =
+                ArgumentCaptor.forClass(CallerInfoLookupHelper.OnQueryCompleteListener.class);
+        verify(mMockCallerInfoLookupHelper).startLookup(any(), listenerCaptor.capture());
+        listenerCaptor.getValue().onCallerInfoQueryComplete(call.getHandle(), info);
+
+        assertEquals(info, call.getCallerInfo());
+        assertEquals(info.getName(), call.getName());
+        assertEquals(info.getPhoneNumber(), call.getPhoneNumber());
+        assertEquals(info.cachedPhoto, call.getPhoto());
+        assertEquals(info.cachedPhotoIcon, call.getPhotoIcon());
+        assertEquals(call.getHandle(), call.getContactUri());
+    }
+
+    @Test
+    @SmallTest
+    public void testOriginalCallIntent() {
+        Call call = createCall("1");
+
+        Intent i = new Intent();
+        call.setOriginalCallIntent(i);
+
+        assertEquals(i, call.getOriginalCallIntent());
+    }
+
+    @Test
+    @SmallTest
+    public void testHandleCreateConferenceSuccessNotifiesListeners() {
+        Call.Listener listener = mock(Call.Listener.class);
+
+        Call incomingCall = createCall("1", Call.CALL_DIRECTION_INCOMING);
+        incomingCall.setConnectionService(mMockConnectionService);
+        incomingCall.addListener(listener);
+        Call outgoingCall = createCall("2", Call.CALL_DIRECTION_OUTGOING);
+        outgoingCall.setConnectionService(mMockConnectionService);
+        outgoingCall.addListener(listener);
+
+        StatusHints statusHints = mock(StatusHints.class);
+        Bundle extra = new Bundle();
+        ParcelableConference conference =
+                new ParcelableConference.Builder(SIM_1_HANDLE, Connection.STATE_NEW)
+                    .setAddress(TEST_ADDRESS, TelecomManager.PRESENTATION_ALLOWED)
+                    .setConnectionCapabilities(123)
+                    .setVideoAttributes(null, VideoProfile.STATE_AUDIO_ONLY)
+                    .setRingbackRequested(true)
+                    .setStatusHints(statusHints)
+                    .setExtras(extra)
+                    .build();
+
+        incomingCall.handleCreateConferenceSuccess(null, conference);
+        verify(listener).onSuccessfulIncomingCall(incomingCall);
+
+        outgoingCall.handleCreateConferenceSuccess(null, conference);
+        verify(listener).onSuccessfulOutgoingCall(outgoingCall, CallState.NEW);
+    }
+
+    @Test
+    @SmallTest
+    public void testHandleCreateConferenceSuccess() {
+        Call call = createCall("1", Call.CALL_DIRECTION_INCOMING);
+        call.setConnectionService(mMockConnectionService);
+
+        StatusHints statusHints = mock(StatusHints.class);
+        Bundle extra = new Bundle();
+        ParcelableConference conference =
+                new ParcelableConference.Builder(SIM_1_HANDLE, Connection.STATE_NEW)
+                    .setAddress(TEST_ADDRESS, TelecomManager.PRESENTATION_ALLOWED)
+                    .setConnectionCapabilities(123)
+                    .setVideoAttributes(null, VideoProfile.STATE_AUDIO_ONLY)
+                    .setRingbackRequested(true)
+                    .setStatusHints(statusHints)
+                    .setExtras(extra)
+                    .build();
+
+        call.handleCreateConferenceSuccess(null, conference);
+
+        assertEquals(SIM_1_HANDLE, call.getTargetPhoneAccount());
+        assertEquals(TEST_ADDRESS, call.getHandle());
+        assertEquals(123, call.getConnectionCapabilities());
+        assertNull(call.getVideoProviderProxy());
+        assertEquals(VideoProfile.STATE_AUDIO_ONLY, call.getVideoState());
+        assertTrue(call.isRingbackRequested());
+        assertEquals(statusHints, call.getStatusHints());
+    }
+
+    @Test
+    @SmallTest
+    public void testHandleCreateConferenceFailure() {
+        Call.Listener listener = mock(Call.Listener.class);
+
+        Call incomingCall = createCall("1", Call.CALL_DIRECTION_INCOMING);
+        incomingCall.setConnectionService(mMockConnectionService);
+        incomingCall.addListener(listener);
+        Call outgoingCall = createCall("2", Call.CALL_DIRECTION_OUTGOING);
+        outgoingCall.setConnectionService(mMockConnectionService);
+        outgoingCall.addListener(listener);
+
+        final DisconnectCause cause = new DisconnectCause(DisconnectCause.REJECTED);
+
+        incomingCall.handleCreateConferenceFailure(cause);
+        assertEquals(cause, incomingCall.getDisconnectCause());
+        verify(listener).onFailedIncomingCall(incomingCall);
+
+        outgoingCall.handleCreateConferenceFailure(cause);
+        assertEquals(cause, outgoingCall.getDisconnectCause());
+        verify(listener).onFailedOutgoingCall(outgoingCall, cause);
+    }
+
+    @Test
+    @SmallTest
+    public void testWasConferencePreviouslyMerged() {
+        Call call = createCall("1");
+        call.setConnectionService(mMockConnectionService);
+        call.setConnectionCapabilities(Connection.CAPABILITY_MERGE_CONFERENCE);
+
+        assertFalse(call.wasConferencePreviouslyMerged());
+
+        call.mergeConference();
+
+        assertTrue(call.wasConferencePreviouslyMerged());
+    }
+
+    @Test
+    @SmallTest
+    public void testSwapConference() {
+        Call.Listener listener = mock(Call.Listener.class);
+
+        Call call = createCall("1");
+        call.setConnectionService(mMockConnectionService);
+        call.setConnectionCapabilities(Connection.CAPABILITY_SWAP_CONFERENCE);
+        call.addListener(listener);
+
+        call.swapConference();
+        assertNull(call.getConferenceLevelActiveCall());
+
+        Call childCall1 = createCall("child1");
+        childCall1.setChildOf(call);
+        call.swapConference();
+        assertEquals(childCall1, call.getConferenceLevelActiveCall());
+
+        Call childCall2 = createCall("child2");
+        childCall2.setChildOf(call);
+        call.swapConference();
+        assertEquals(childCall1, call.getConferenceLevelActiveCall());
+        call.swapConference();
+        assertEquals(childCall2, call.getConferenceLevelActiveCall());
+
+        verify(listener, times(4)).onCdmaConferenceSwap(call);
+    }
+
+    @Test
+    @SmallTest
+    public void testHandleCreateConnectionFailure() {
+        Call.Listener listener = mock(Call.Listener.class);
+
+        Call incomingCall = createCall("1", Call.CALL_DIRECTION_INCOMING);
+        incomingCall.setConnectionService(mMockConnectionService);
+        incomingCall.addListener(listener);
+        Call outgoingCall = createCall("2", Call.CALL_DIRECTION_OUTGOING);
+        outgoingCall.setConnectionService(mMockConnectionService);
+        outgoingCall.addListener(listener);
+        Call unknownCall = createCall("3", Call.CALL_DIRECTION_UNKNOWN);
+        unknownCall.setConnectionService(mMockConnectionService);
+        unknownCall.addListener(listener);
+
+        final DisconnectCause cause = new DisconnectCause(DisconnectCause.REJECTED);
+
+        incomingCall.handleCreateConnectionFailure(cause);
+        assertEquals(cause, incomingCall.getDisconnectCause());
+        verify(listener).onFailedIncomingCall(incomingCall);
+
+        outgoingCall.handleCreateConnectionFailure(cause);
+        assertEquals(cause, outgoingCall.getDisconnectCause());
+        verify(listener).onFailedOutgoingCall(outgoingCall, cause);
+
+        unknownCall.handleCreateConnectionFailure(cause);
+        assertEquals(cause, unknownCall.getDisconnectCause());
+        verify(listener).onFailedUnknownCall(unknownCall);
+    }
+
+    /**
+     * ensure a Call object does not throw an NPE when the CallingPackageIdentity is not set and
+     * the correct values are returned when set
+     */
+    @Test
+    @SmallTest
+    public void testCallingPackageIdentity() {
+        final int packageUid = 123;
+        final int packagePid = 1;
+
+        Call call = createCall("1");
+
+        // assert default values for a Calls CallingPackageIdentity are -1 unless set via the setter
+        assertEquals(-1, call.getCallingPackageIdentity().mCallingPackageUid);
+        assertEquals(-1, call.getCallingPackageIdentity().mCallingPackagePid);
+
+        // set the Call objects CallingPackageIdentity via the setter and a bundle
+        Bundle extras = new Bundle();
+        extras.putInt(CallAttributes.CALLER_UID_KEY, packageUid);
+        extras.putInt(CallAttributes.CALLER_PID_KEY, packagePid);
+        // assert that the setter removed the extras
+        assertEquals(packageUid, extras.getInt(CallAttributes.CALLER_UID_KEY));
+        assertEquals(packagePid, extras.getInt(CallAttributes.CALLER_PID_KEY));
+        call.setCallingPackageIdentity(extras);
+        // assert that the setter removed the extras
+        assertEquals(0, extras.getInt(CallAttributes.CALLER_UID_KEY));
+        assertEquals(0, extras.getInt(CallAttributes.CALLER_PID_KEY));
+        // assert the properties are fetched correctly
+        assertEquals(packageUid, call.getCallingPackageIdentity().mCallingPackageUid);
+        assertEquals(packagePid, call.getCallingPackageIdentity().mCallingPackagePid);
+    }
+
+        @Test
+    @SmallTest
+    public void testOnConnectionEventNotifiesListener() {
+        Call.Listener listener = mock(Call.Listener.class);
+        Call call = createCall("1");
+        call.addListener(listener);
+
+        call.onConnectionEvent(Connection.EVENT_ON_HOLD_TONE_START, null);
+        verify(listener).onHoldToneRequested(call);
+        assertTrue(call.isRemotelyHeld());
+
+        call.onConnectionEvent(Connection.EVENT_ON_HOLD_TONE_END, null);
+        verify(listener, times(2)).onHoldToneRequested(call);
+        assertFalse(call.isRemotelyHeld());
+
+        call.onConnectionEvent(Connection.EVENT_CALL_HOLD_FAILED, null);
+        verify(listener).onCallHoldFailed(call);
+
+        call.onConnectionEvent(Connection.EVENT_CALL_SWITCH_FAILED, null);
+        verify(listener).onCallSwitchFailed(call);
+
+        final int d2dType = 1;
+        final int d2dValue = 2;
+        final Bundle d2dExtras = new Bundle();
+        d2dExtras.putInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_TYPE, d2dType);
+        d2dExtras.putInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_VALUE, d2dValue);
+        call.onConnectionEvent(Connection.EVENT_DEVICE_TO_DEVICE_MESSAGE, d2dExtras);
+        verify(listener).onReceivedDeviceToDeviceMessage(call, d2dType, d2dValue);
+
+        final CallQuality quality = new CallQuality();
+        final Bundle callQualityExtras = new Bundle();
+        callQualityExtras.putParcelable(Connection.EXTRA_CALL_QUALITY_REPORT, quality);
+        call.onConnectionEvent(Connection.EVENT_CALL_QUALITY_REPORT, callQualityExtras);
+        verify(listener).onReceivedCallQualityReport(call, quality);
+    }
+
+    @Test
+    @SmallTest
+    public void testDiagnosticMessage() {
+        Call.Listener listener = mock(Call.Listener.class);
+        Call call = createCall("1");
+        call.addListener(listener);
+
+        final int id = 1;
+        final String message = "msg";
+
+        call.displayDiagnosticMessage(id, message);
+        verify(listener).onConnectionEvent(
+                eq(call),
+                eq(android.telecom.Call.EVENT_DISPLAY_DIAGNOSTIC_MESSAGE),
+                argThat(extras -> {
+                    return extras.getInt(android.telecom.Call.EXTRA_DIAGNOSTIC_MESSAGE_ID) == id &&
+                            extras.getCharSequence(android.telecom.Call.EXTRA_DIAGNOSTIC_MESSAGE)
+                                .toString().equals(message);
+                }));
+
+        call.clearDiagnosticMessage(id);
+        verify(listener).onConnectionEvent(
+                eq(call),
+                eq(android.telecom.Call.EVENT_CLEAR_DIAGNOSTIC_MESSAGE),
+                argThat(extras -> {
+                    return extras.getInt(android.telecom.Call.EXTRA_DIAGNOSTIC_MESSAGE_ID) == id;
+                }));
+    }
+
+    private Call createCall(String id) {
+        return createCall(id, Call.CALL_DIRECTION_UNDEFINED);
+    }
+
+    private Call createCall(String id, int callDirection) {
+        return new Call(
+                id,
+                mContext,
+                mMockCallsManager,
+                mLock,
+                null,
+                mMockPhoneNumberUtilsAdapter,
+                TEST_ADDRESS,
+                null /* GatewayInfo */,
+                null,
+                SIM_1_HANDLE,
+                callDirection,
+                false,
+                false,
+                mMockClockProxy,
+                mMockToastProxy);
     }
 }
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index 160114a..c42a2ca 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.telecom.tests;
 
+import static android.provider.CallLog.Calls.USER_MISSED_NOT_RUNNING;
+
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.TestCase.fail;
 
@@ -42,17 +44,26 @@
 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.os.UserManager;
+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 +72,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 +98,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 +113,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 +121,13 @@
 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.CallStreamingNotification;
 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 +143,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;
@@ -131,7 +153,10 @@
 @RunWith(JUnit4.class)
 public class CallsManagerTest extends TelecomTestCase {
     private static final int TEST_TIMEOUT = 5000;  // milliseconds
+    private static final long STATE_TIMEOUT = 5000L;
     private static final int SECONDARY_USER_ID = 12;
+    private static final UserHandle TEST_USER_HANDLE = UserHandle.of(123);
+    private static final String TEST_PACKAGE_NAME = "GoogleMeet";
     private static final PhoneAccountHandle SIM_1_HANDLE = new PhoneAccountHandle(
             ComponentName.unflattenFromString("com.foo/.Blah"), "Sim1");
     private static final PhoneAccountHandle SIM_1_HANDLE_SECONDARY = new PhoneAccountHandle(
@@ -147,12 +172,23 @@
             ComponentName.unflattenFromString("com.voip/.Stuff"), "Voip1");
     private static final PhoneAccountHandle SELF_MANAGED_HANDLE = new PhoneAccountHandle(
             ComponentName.unflattenFromString("com.baz/.Self"), "Self");
+    private static final PhoneAccountHandle SELF_MANAGED_2_HANDLE = new PhoneAccountHandle(
+            ComponentName.unflattenFromString("com.baz/.Self2"), "Self2");
+    private static final PhoneAccountHandle SELF_MANAGED_W_CUSTOM_HANDLE = new PhoneAccountHandle(
+            new ComponentName(TEST_PACKAGE_NAME, "class"), "1", TEST_USER_HANDLE);
     private static final PhoneAccount SIM_1_ACCOUNT = new PhoneAccount.Builder(SIM_1_HANDLE, "Sim1")
             .setCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION
                     | PhoneAccount.CAPABILITY_CALL_PROVIDER
                     | 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
@@ -164,6 +200,16 @@
             .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
             .setIsEnabled(true)
             .build();
+    private static final PhoneAccount SELF_MANAGED_2_ACCOUNT = new PhoneAccount.Builder(
+            SELF_MANAGED_2_HANDLE, "Self2")
+            .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
+            .setIsEnabled(true)
+            .build();
+    private static final PhoneAccount SM_W_DIFFERENT_PACKAGE_AND_USER = new PhoneAccount.Builder(
+            SELF_MANAGED_W_CUSTOM_HANDLE, "Self")
+            .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
+            .setIsEnabled(true)
+            .build();
     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");
@@ -171,6 +217,8 @@
             TEST_ADDRESS2, SIM_1_HANDLE,
             TEST_ADDRESS3, SIM_2_HANDLE);
 
+    private static final String DEFAULT_CALL_SCREENING_APP = "com.foo.call_screen_app";
+
     private static int sCallId = 1;
     private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
     @Mock private CallerInfoLookupHelper mCallerInfoLookupHelper;
@@ -199,6 +247,8 @@
     @Mock private AudioProcessingNotification mAudioProcessingNotification;
     @Mock private InCallControllerFactory mInCallControllerFactory;
     @Mock private InCallController mInCallController;
+    @Mock private CallEndpointControllerFactory mCallEndpointControllerFactory;
+    @Mock private CallEndpointController mCallEndpointController;
     @Mock private ConnectionServiceFocusManager mConnectionSvrFocusMgr;
     @Mock private CallAudioRouteStateMachine mCallAudioRouteStateMachine;
     @Mock private CallAudioRouteStateMachine.Factory mCallAudioRouteStateMachineFactory;
@@ -209,6 +259,14 @@
     @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;
+    @Mock private CallStreamingNotification mCallStreamingNotification;
 
     private CallsManager mCallsManager;
 
@@ -225,8 +283,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());
@@ -237,6 +297,9 @@
                 .thenReturn(mDisconnectedCallNotifier);
         when(mTimeoutsAdapter.getCallDiagnosticServiceTimeoutMillis(any(ContentResolver.class)))
                 .thenReturn(2000L);
+        when(mTimeoutsAdapter.getNonVoipCallTransitoryStateTimeoutMillis())
+                .thenReturn(STATE_TIMEOUT);
+        when(mClockProxy.elapsedRealtime()).thenReturn(0L);
         mCallsManager = new CallsManager(
                 mComponentContextFixture.getTestDouble().getApplicationContext(),
                 mLock,
@@ -266,7 +329,16 @@
                 mInCallControllerFactory,
                 mCallDiagnosticServiceController,
                 mRoleManagerAdapter,
-                mToastFactory);
+                mToastFactory,
+                mCallEndpointControllerFactory,
+                mCallAnomalyWatchdog,
+                mAccessibilityManagerAdapter,
+                // Just do async tasks synchronously to support testing.
+                command -> command.run(),
+                mBlockedNumbersAdapter,
+                TransactionManager.getTestInstance(),
+                mEmergencyCallDiagnosticLogger,
+                mCallStreamingNotification);
 
         when(mPhoneAccountRegistrar.getPhoneAccount(
                 eq(SELF_MANAGED_HANDLE), any())).thenReturn(SELF_MANAGED_ACCOUNT);
@@ -291,6 +363,26 @@
         assertEquals(0, mCallsManager.constructPossiblePhoneAccounts(null, null, false, false).size());
     }
 
+    private Call constructOngoingCall(String callId, PhoneAccountHandle phoneAccountHandle) {
+        Call ongoingCall = new Call(
+                callId,
+                mContext,
+                mCallsManager,
+                mLock,
+                null /* ConnectionServiceRepository */,
+                mPhoneNumberUtilsAdapter,
+                TEST_ADDRESS,
+                null /* GatewayInfo */,
+                null /* connectionManagerPhoneAccountHandle */,
+                phoneAccountHandle,
+                Call.CALL_DIRECTION_INCOMING,
+                false /* shouldAttachToExistingConnection*/,
+                false /* isConference */,
+                mClockProxy,
+                mToastFactory);
+        ongoingCall.setState(CallState.ACTIVE, "just cuz");
+        return ongoingCall;
+    }
     /**
      * Verify behavior for multisim devices where we want to ensure that the active sim is used for
      * placing a new call.
@@ -301,23 +393,7 @@
     public void testConstructPossiblePhoneAccountsMultiSimActive() throws Exception {
         setupMsimAccounts();
 
-        Call ongoingCall = new Call(
-                "1", /* callId */
-                mContext,
-                mCallsManager,
-                mLock,
-                null /* ConnectionServiceRepository */,
-                mPhoneNumberUtilsAdapter,
-                TEST_ADDRESS,
-                null /* GatewayInfo */,
-                null /* connectionManagerPhoneAccountHandle */,
-                SIM_2_HANDLE,
-                Call.CALL_DIRECTION_INCOMING,
-                false /* shouldAttachToExistingConnection*/,
-                false /* isConference */,
-                mClockProxy,
-                mToastFactory);
-        ongoingCall.setState(CallState.ACTIVE, "just cuz");
+        Call ongoingCall = constructOngoingCall("1", SIM_2_HANDLE);
         mCallsManager.addCall(ongoingCall);
 
         List<PhoneAccountHandle> phoneAccountHandles = mCallsManager.constructPossiblePhoneAccounts(
@@ -340,6 +416,47 @@
         assertEquals(2, phoneAccountHandles.size());
     }
 
+    /**
+     * For DSDA-enabled multisim devices with an ongoing call, verify that both SIMs'
+     * PhoneAccountHandles are constructed while placing a new call.
+     * @throws Exception
+     */
+    @MediumTest
+    @Test
+    public void testConstructPossiblePhoneAccountsMultiSimActive_dsdaCallingPossible() throws
+            Exception {
+        setupMsimAccounts();
+        setMaxActiveVoiceSubscriptions(2);
+
+        Call ongoingCall = constructOngoingCall("1", SIM_2_HANDLE);
+        mCallsManager.addCall(ongoingCall);
+
+        List<PhoneAccountHandle> phoneAccountHandles = mCallsManager.constructPossiblePhoneAccounts(
+                TEST_ADDRESS, null, false, false);
+        assertEquals(2, phoneAccountHandles.size());
+    }
+
+    /**
+     * For DSDA-enabled multisim devices with an ongoing call, verify that only the active SIMs'
+     * PhoneAccountHandle is constructed while placing an emergency call.
+     * @throws Exception
+     */
+    @MediumTest
+    @Test
+    public void testConstructPossiblePhoneAccountsMultiSimActive_dsdaCallingPossible_emergencyCall()
+            throws Exception {
+        setupMsimAccounts();
+        setMaxActiveVoiceSubscriptions(2);
+
+        Call ongoingCall = constructOngoingCall("1", SIM_2_HANDLE);
+        mCallsManager.addCall(ongoingCall);
+
+        List<PhoneAccountHandle> phoneAccountHandles = mCallsManager.constructPossiblePhoneAccounts(
+                TEST_ADDRESS, null, false, true /* isEmergency */);
+        assertEquals(1, phoneAccountHandles.size());
+        assertEquals(SIM_2_HANDLE, phoneAccountHandles.get(0));
+    }
+
     private void setupCallerInfoLookupHelper() {
         doAnswer(invocation -> {
             Uri handle = invocation.getArgument(0);
@@ -383,7 +500,7 @@
         when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
                 SIM_1_HANDLE);
         when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
-                any(), anyInt(), anyInt())).thenReturn(
+                any(), anyInt(), anyInt(), anyBoolean())).thenReturn(
                 new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
 
         List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
@@ -407,7 +524,7 @@
         when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
                 null);
         when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
-                any(), anyInt(), anyInt())).thenReturn(
+                any(), anyInt(), anyInt(), anyBoolean())).thenReturn(
                 new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
 
         List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
@@ -431,8 +548,8 @@
         when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
                 null);
         when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
-                any(), eq(PhoneAccount.CAPABILITY_VIDEO_CALLING), anyInt())).thenReturn(
-                new ArrayList<>(Arrays.asList(SIM_2_HANDLE)));
+                any(), eq(PhoneAccount.CAPABILITY_VIDEO_CALLING), anyInt(), anyBoolean()))
+                .thenReturn(new ArrayList<>(Arrays.asList(SIM_2_HANDLE)));
 
         List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
                 null /* phoneAcct */, TEST_ADDRESS, true /* isVideo */, false /* isEmergency */, null /* userHandle */)
@@ -455,11 +572,11 @@
                 null);
         // When querying for video capable accounts, return nothing.
         when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
-                any(), eq(PhoneAccount.CAPABILITY_VIDEO_CALLING), anyInt())).thenReturn(
-                Collections.emptyList());
+                any(), eq(PhoneAccount.CAPABILITY_VIDEO_CALLING), anyInt(), anyBoolean())).
+                thenReturn(Collections.emptyList());
         // When querying for non-video capable accounts, return one.
         when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
-                any(), eq(0 /* none specified */), anyInt())).thenReturn(
+                any(), eq(0 /* none specified */), anyInt(), anyBoolean())).thenReturn(
                 new ArrayList<>(Arrays.asList(SIM_1_HANDLE)));
         List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
                 null /* phoneAcct */, TEST_ADDRESS, true /* isVideo */, false /* isEmergency */, null /* userHandle */)
@@ -481,7 +598,7 @@
         when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
                 null);
         when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
-                any(), anyInt(), anyInt())).thenReturn(
+                any(), anyInt(), anyInt(), anyBoolean())).thenReturn(
                 new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
 
         List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
@@ -502,7 +619,7 @@
         when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
                 null);
         when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
-                any(), anyInt(), anyInt())).thenReturn(
+                any(), anyInt(), anyInt(), anyBoolean())).thenReturn(
                 new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
 
         List<PhoneAccountHandle> accounts = mCallsManager.findOutgoingCallPhoneAccount(
@@ -597,6 +714,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() {
@@ -675,7 +840,7 @@
         mCallsManager.answerCall(incomingCall, VideoProfile.STATE_AUDIO_ONLY);
 
         // THEN the ongoing call is held and the focus request for incoming call is sent
-        verify(ongoingCall).hold();
+        verify(ongoingCall).hold(anyString());
         verifyFocusRequestAndExecuteCallback(incomingCall);
 
         // and the incoming call is answered.
@@ -785,6 +950,64 @@
 
     @SmallTest
     @Test
+    public void testAnswerThirdCallWhenTwoCallsOnDifferentSims_disconnectsHeldCall() {
+        // Given an ongoing call on SIM1
+        Call ongoingCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
+        doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
+        doReturn(CallState.ACTIVE).when(ongoingCall).getState();
+        when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
+
+        // And a held call on SIM2, which belongs to the same ConnectionService
+        Call heldCall = addSpyCall(SIM_2_HANDLE, CallState.ON_HOLD);
+        doReturn(CallState.ON_HOLD).when(heldCall).getState();
+
+        // on answering an incoming call on SIM1, which belongs to the same ConnectionService
+        Call incomingCall = addSpyCall(SIM_1_HANDLE, CallState.RINGING);
+        mCallsManager.answerCall(incomingCall, VideoProfile.STATE_AUDIO_ONLY);
+
+        // THEN the previous held call is disconnected
+        verify(heldCall).disconnect();
+
+        // and the ongoing call is held
+        verify(ongoingCall).hold();
+
+        // and the focus request is sent
+        verifyFocusRequestAndExecuteCallback(incomingCall);
+        // and the incoming call is answered
+        verify(incomingCall).answer(VideoProfile.STATE_AUDIO_ONLY);
+    }
+
+    @SmallTest
+    @Test
+    public void testAnswerThirdCallDifferentSimWhenTwoCallsOnSameSim_disconnectsHeldCall() {
+        // Given an ongoing call on SIM1
+        Call ongoingCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
+        doReturn(true).when(ongoingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
+        doReturn(CallState.ACTIVE).when(ongoingCall).getState();
+        when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
+
+        // And a held call on SIM1
+        Call heldCall = addSpyCall(SIM_1_HANDLE, CallState.ON_HOLD);
+        doReturn(CallState.ON_HOLD).when(heldCall).getState();
+
+        // on answering an incoming call on SIM2, which belongs to the same ConnectionService
+        Call incomingCall = addSpyCall(SIM_2_HANDLE, CallState.RINGING);
+        mCallsManager.answerCall(incomingCall, VideoProfile.STATE_AUDIO_ONLY);
+
+        // THEN the previous held call is disconnected
+        verify(heldCall).disconnect();
+
+        // and the ongoing call is held
+        verify(ongoingCall).hold();
+
+        // and the focus request is sent
+        verifyFocusRequestAndExecuteCallback(incomingCall);
+        // and the incoming call is answered
+        verify(incomingCall).answer(VideoProfile.STATE_AUDIO_ONLY);
+    }
+
+    @SmallTest
+    @Test
     public void testAnswerCallWhenNoOngoingCallExisted() {
         // GIVEN a CallsManager with no ongoing call.
 
@@ -883,7 +1106,7 @@
         mCallsManager.markCallAsActive(newCall);
 
         // THEN the ongoing call is held
-        verify(ongoingCall).hold();
+        verify(ongoingCall).hold(anyString());
         verifyFocusRequestAndExecuteCallback(newCall);
 
         // and the new call is active
@@ -942,6 +1165,43 @@
 
     @SmallTest
     @Test
+    public void testNoFilteringOfNetworkIdentifiedEmergencyCalls() {
+        // GIVEN an incoming call which is network identified as an emergency call.
+        Call incomingCall = addSpyCall(CallState.NEW);
+        incomingCall.setConnectionProperties(Connection.PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL);
+        doReturn(false).when(incomingCall).can(Connection.CAPABILITY_HOLD);
+        doReturn(false).when(incomingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
+        doReturn(true).when(incomingCall)
+                .hasProperty(Connection.PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL);
+        doReturn(true).when(incomingCall).setState(anyInt(), any());
+
+        // WHEN the incoming call is successfully added.
+        mCallsManager.onSuccessfulIncomingCall(incomingCall);
+
+        // THEN the incoming call is not using call filtering
+        verify(incomingCall).setIsUsingCallFiltering(eq(false));
+    }
+
+    @SmallTest
+    @Test
+    public void testNoFilteringOfEmergencySmsModeCalls() {
+        // GIVEN an incoming call which is network identified as an emergency call.
+        Call incomingCall = addSpyCall(CallState.NEW);
+        when(mComponentContextFixture.getTelephonyManager().isInEmergencySmsMode())
+                .thenReturn(true);
+        doReturn(false).when(incomingCall).can(Connection.CAPABILITY_HOLD);
+        doReturn(false).when(incomingCall).can(Connection.CAPABILITY_SUPPORT_HOLD);
+        doReturn(true).when(incomingCall).setState(anyInt(), any());
+
+        // WHEN the incoming call is successfully added.
+        mCallsManager.onSuccessfulIncomingCall(incomingCall);
+
+        // THEN the incoming call is not using call filtering
+        verify(incomingCall).setIsUsingCallFiltering(eq(false));
+    }
+
+    @SmallTest
+    @Test
     public void testAcceptIncomingCallWhenHeadsetMediaButtonShortPress() {
         // GIVEN an incoming call
         Call incomingCall = addSpyCall();
@@ -1141,7 +1401,7 @@
         when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
                 SIM_1_HANDLE);
         when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
-                any(), anyInt(), anyInt())).thenReturn(
+                any(), anyInt(), anyInt(), anyBoolean())).thenReturn(
                 new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
         mCallsManager.addConnectionServiceRepositoryCache(SIM_2_HANDLE.getComponentName(),
                 SIM_2_HANDLE.getUserHandle(), service);
@@ -1169,13 +1429,132 @@
         when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(SIM_1_HANDLE))
                 .thenReturn(SIM_1_ACCOUNT);
 
-        assertFalse(mCallsManager.isIncomingCallPermitted(null, SELF_MANAGED_HANDLE));
-        assertFalse(mCallsManager.isIncomingCallPermitted(null, SIM_1_HANDLE));
+        assertFalse(mCallsManager.isIncomingCallPermitted(SELF_MANAGED_HANDLE));
+        assertFalse(mCallsManager.isIncomingCallPermitted(SIM_1_HANDLE));
+    }
+
+    @MediumTest
+    @Test
+    public void testManagedIncomingCallPermitted() {
+        when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(SIM_1_HANDLE))
+                .thenReturn(SIM_1_ACCOUNT);
+
+        // Don't care
+        Call selfManagedCall = addSpyCall(SELF_MANAGED_HANDLE, CallState.ACTIVE);
+        when(selfManagedCall.isSelfManaged()).thenReturn(true);
+        assertTrue(mCallsManager.isIncomingCallPermitted(SIM_1_HANDLE));
+
+        Call existingCall = addSpyCall(SIM_1_HANDLE, CallState.NEW);
+        when(existingCall.isSelfManaged()).thenReturn(false);
+
+        when(existingCall.getState()).thenReturn(CallState.RINGING);
+        assertFalse(mCallsManager.isIncomingCallPermitted(SIM_1_HANDLE));
+
+        when(existingCall.getState()).thenReturn(CallState.ON_HOLD);
+        assertFalse(mCallsManager.isIncomingCallPermitted(SIM_1_HANDLE));
+    }
+
+    @MediumTest
+    @Test
+    public void testSelfManagedIncomingCallPermitted() {
+        when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(SELF_MANAGED_HANDLE))
+                .thenReturn(SELF_MANAGED_ACCOUNT);
+
+        // Don't care
+        Call managedCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
+        when(managedCall.isSelfManaged()).thenReturn(false);
+        assertTrue(mCallsManager.isIncomingCallPermitted(SELF_MANAGED_HANDLE));
+
+        Call existingCall = addSpyCall(SELF_MANAGED_HANDLE, CallState.RINGING);
+        when(existingCall.isSelfManaged()).thenReturn(true);
+        assertFalse(mCallsManager.isIncomingCallPermitted(SELF_MANAGED_HANDLE));
+
+        when(existingCall.getState()).thenReturn(CallState.ACTIVE);
+        assertTrue(mCallsManager.isIncomingCallPermitted(SELF_MANAGED_HANDLE));
+
+        // Add self managed calls up to 10
+        for (int i = 0; i < 9; i++) {
+            Call selfManagedCall = addSpyCall(SELF_MANAGED_HANDLE, CallState.ON_HOLD);
+            when(selfManagedCall.isSelfManaged()).thenReturn(true);
+        }
+        assertFalse(mCallsManager.isIncomingCallPermitted(SELF_MANAGED_HANDLE));
     }
 
     @SmallTest
     @Test
-    public void testMakeRoomForOutgoingCallAudioProcessingInProgress() {
+    public void testManagedOutgoingCallPermitted() {
+        when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(SIM_1_HANDLE))
+                .thenReturn(SIM_1_ACCOUNT);
+
+        // Don't care
+        Call selfManagedCall = addSpyCall(SELF_MANAGED_HANDLE, CallState.ACTIVE);
+        when(selfManagedCall.isSelfManaged()).thenReturn(true);
+        assertTrue(mCallsManager.isOutgoingCallPermitted(SIM_1_HANDLE));
+
+        Call existingCall = addSpyCall(SIM_1_HANDLE, CallState.NEW);
+        when(existingCall.isSelfManaged()).thenReturn(false);
+
+        when(existingCall.getState()).thenReturn(CallState.CONNECTING);
+        assertFalse(mCallsManager.isOutgoingCallPermitted(SIM_1_HANDLE));
+
+        when(existingCall.getState()).thenReturn(CallState.DIALING);
+        assertFalse(mCallsManager.isOutgoingCallPermitted(SIM_1_HANDLE));
+
+        when(existingCall.getState()).thenReturn(CallState.ACTIVE);
+        assertFalse(mCallsManager.isOutgoingCallPermitted(SIM_1_HANDLE));
+
+        when(existingCall.getState()).thenReturn(CallState.ON_HOLD);
+        assertFalse(mCallsManager.isOutgoingCallPermitted(SIM_1_HANDLE));
+    }
+
+    @SmallTest
+    @Test
+    public void testSelfManagedOutgoingCallPermitted() {
+        when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(SELF_MANAGED_HANDLE))
+                .thenReturn(SELF_MANAGED_ACCOUNT);
+
+        // Don't care
+        Call managedCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
+        when(managedCall.isSelfManaged()).thenReturn(false);
+        assertTrue(mCallsManager.isOutgoingCallPermitted(SELF_MANAGED_HANDLE));
+
+        Call ongoingCall = addSpyCall(SELF_MANAGED_HANDLE, CallState.ACTIVE);
+        when(ongoingCall.isSelfManaged()).thenReturn(true);
+        when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(ongoingCall);
+
+        when(ongoingCall.can(Connection.CAPABILITY_HOLD)).thenReturn(false);
+        assertFalse(mCallsManager.isOutgoingCallPermitted(SELF_MANAGED_HANDLE));
+
+        when(ongoingCall.can(Connection.CAPABILITY_HOLD)).thenReturn(true);
+        assertTrue(mCallsManager.isOutgoingCallPermitted(SELF_MANAGED_HANDLE));
+
+        Call handoverCall = addSpyCall(SELF_MANAGED_HANDLE, CallState.NEW);
+        when(handoverCall.isSelfManaged()).thenReturn(true);
+        when(handoverCall.getHandoverSourceCall()).thenReturn(mock(Call.class));
+        assertTrue(mCallsManager.isOutgoingCallPermitted(handoverCall, SELF_MANAGED_HANDLE));
+
+        // Add self managed calls up to 10
+        for (int i = 0; i < 8; i++) {
+            Call selfManagedCall = addSpyCall(SELF_MANAGED_HANDLE, CallState.ON_HOLD);
+            when(selfManagedCall.isSelfManaged()).thenReturn(true);
+        }
+        assertFalse(mCallsManager.isOutgoingCallPermitted(SELF_MANAGED_HANDLE));
+    }
+
+    @SmallTest
+    @Test
+    public void testSelfManagedOutgoingCallPermittedHasEmergencyCall() {
+        when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(SELF_MANAGED_HANDLE))
+                .thenReturn(SELF_MANAGED_ACCOUNT);
+
+        Call emergencyCall = addSpyCall();
+        when(emergencyCall.isEmergencyCall()).thenReturn(true);
+        assertFalse(mCallsManager.isOutgoingCallPermitted(SELF_MANAGED_HANDLE));
+    }
+
+    @SmallTest
+    @Test
+    public void testMakeRoomForEmergencyCallAudioProcessingInProgress() {
         Call ongoingCall = addSpyCall(SIM_2_HANDLE, CallState.AUDIO_PROCESSING);
 
         Call newEmergencyCall = createCall(SIM_1_HANDLE, CallState.NEW);
@@ -1190,7 +1569,7 @@
 
     @SmallTest
     @Test
-    public void testMakeRoomForEmergencyDuringIncomingCall() {
+    public void testMakeRoomForEmergencyCallDuringIncomingCall() {
         Call ongoingCall = addSpyCall(SIM_2_HANDLE, CallState.RINGING);
 
         Call newEmergencyCall = createCall(SIM_1_HANDLE, CallState.NEW);
@@ -1253,21 +1632,230 @@
         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);
 
+        // Make sure enough time has passed that we'd drop the connecting call.
+        when(mClockProxy.elapsedRealtime()).thenReturn(STATE_TIMEOUT + 10L);
+        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());
+    }
+
+    /**
+     * Verifies that we won't auto-disconnect an outgoing CONNECTING call unless it has timed out.
+     */
+    @SmallTest
+    @Test
+    public void testDontDisconnectConnectingCallWhenNotTimedOut() {
+        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(TEST_ADDRESS, TelecomManager.PRESENTATION_ALLOWED);
+
+        // Make sure it has been a short time so we don't try to disconnect the call
+        when(mClockProxy.elapsedRealtime()).thenReturn(STATE_TIMEOUT / 2);
+        assertFalse(mCallsManager.makeRoomForOutgoingCall(newCall));
+        verify(ongoingCall, never()).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);
+
+        when(mClockProxy.elapsedRealtime()).thenReturn(STATE_TIMEOUT + 10L);
         assertTrue(mCallsManager.makeRoomForOutgoingCall(newCall));
         verify(ongoingCall).disconnect(anyLong(), anyString());
     }
 
+    @SmallTest
+    @Test
+    public void testMakeRoomForOutgoingCallForSameCall() {
+        addSpyCall(SIM_2_HANDLE, CallState.CONNECTING);
+        Call ongoingCall2 = addSpyCall();
+        when(mClockProxy.elapsedRealtime()).thenReturn(STATE_TIMEOUT + 10L);
+        assertTrue(mCallsManager.makeRoomForOutgoingCall(ongoingCall2));
+    }
+
+    /**
+     * Test where a VoIP app adds another new call and has one active already; ensure we hold the
+     * active call.  This assumes same connection service in the same app.
+     */
+    @SmallTest
+    @Test
+    public void testMakeRoomForOutgoingCallForSameVoipApp() {
+        Call activeCall = addSpyCall(SELF_MANAGED_HANDLE, null /* connMgr */,
+                CallState.ACTIVE, Connection.CAPABILITY_HOLD | Connection.CAPABILITY_SUPPORT_HOLD,
+                0 /* properties */);
+        Call newDialingCall = createCall(SELF_MANAGED_HANDLE, CallState.DIALING);
+        newDialingCall.setConnectionProperties(Connection.CAPABILITY_HOLD
+                        | Connection.CAPABILITY_SUPPORT_HOLD);
+        assertTrue(mCallsManager.makeRoomForOutgoingCall(newDialingCall));
+        verify(activeCall).hold(anyString());
+    }
+
+    /**
+     * Test where a VoIP app adds another new call and has one active already; ensure we hold the
+     * active call.  This assumes different connection services in the same app.
+     */
+    @SmallTest
+    @Test
+    public void testMakeRoomForOutgoingCallForSameVoipAppDifferentConnectionService() {
+        Call activeCall = addSpyCall(SELF_MANAGED_HANDLE, null /* connMgr */,
+                CallState.ACTIVE, Connection.CAPABILITY_HOLD | Connection.CAPABILITY_SUPPORT_HOLD,
+                0 /* properties */);
+        Call newDialingCall = createCall(SELF_MANAGED_2_HANDLE, CallState.DIALING);
+        newDialingCall.setConnectionProperties(Connection.CAPABILITY_HOLD
+                | Connection.CAPABILITY_SUPPORT_HOLD);
+        assertTrue(mCallsManager.makeRoomForOutgoingCall(newDialingCall));
+        verify(activeCall).hold(anyString());
+    }
+
+    /**
+     * Test where a VoIP app adds another new call and has one active already; ensure we hold the
+     * active call.  This assumes different connection services in the same app.
+     */
+    @SmallTest
+    @Test
+    public void testMakeRoomForOutgoingCallForSameNonVoipApp() {
+        Call activeCall = addSpyCall(SIM_1_HANDLE, null /* connMgr */,
+                CallState.ACTIVE, Connection.CAPABILITY_HOLD | Connection.CAPABILITY_SUPPORT_HOLD,
+                0 /* properties */);
+        Call newDialingCall = createCall(SIM_1_HANDLE, CallState.DIALING);
+        newDialingCall.setConnectionProperties(Connection.CAPABILITY_HOLD
+                | Connection.CAPABILITY_SUPPORT_HOLD);
+        assertTrue(mCallsManager.makeRoomForOutgoingCall(newDialingCall));
+        verify(activeCall, never()).hold(anyString());
+    }
+
+    @SmallTest
+    @Test
+    public void testMakeRoomForOutgoingCallHasOutgoingCallSelectingAccount() {
+        Call outgoingCall = addSpyCall(SIM_1_HANDLE, CallState.SELECT_PHONE_ACCOUNT);
+        Call newCall = createSpyCall(SIM_1_HANDLE, CallState.NEW);
+
+        assertTrue(mCallsManager.makeRoomForOutgoingCall(newCall));
+        verify(outgoingCall).disconnect(anyString());
+    }
+
+    @SmallTest
+    @Test
+    public void testMakeRoomForOutgoingCallHasDialingCall() {
+        addSpyCall(SIM_1_HANDLE, CallState.DIALING);
+        Call newCall = createSpyCall(SIM_1_HANDLE, CallState.NEW);
+
+        assertFalse(mCallsManager.makeRoomForOutgoingCall(newCall));
+    }
+
+    @MediumTest
+    @Test
+    public void testMakeRoomForOutgoingCallHasHoldableCall() {
+        Call holdableCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
+        when(holdableCall.can(Connection.CAPABILITY_HOLD)).thenReturn(true);
+
+        Call newCall = createSpyCall(CONNECTION_MGR_1_HANDLE, CallState.NEW);
+
+        assertTrue(mCallsManager.makeRoomForOutgoingCall(newCall));
+        verify(holdableCall).hold(anyString());
+    }
+
+    @SmallTest
+    @Test
+    public void testMakeRoomForOutgoingCallHasUnholdableCall() {
+        Call holdableCall = addSpyCall(SIM_1_HANDLE, CallState.ACTIVE);
+        when(holdableCall.can(Connection.CAPABILITY_HOLD)).thenReturn(false);
+
+        Call newCall = createSpyCall(CONNECTION_MGR_1_HANDLE, CallState.NEW);
+
+        assertFalse(mCallsManager.makeRoomForOutgoingCall(newCall));
+    }
+
     /**
      * Verifies that changes to a {@link PhoneAccount}'s
      * {@link PhoneAccount#CAPABILITY_VIDEO_CALLING} capability will be reflected on a call.
@@ -1399,6 +1987,7 @@
         Call screenedCall = mock(Call.class);
         Bundle extra = new Bundle();
         when(screenedCall.getIntentExtras()).thenReturn(extra);
+        when(screenedCall.getTargetPhoneAccount()).thenReturn(SIM_1_HANDLE);
         String appName = "blah";
         CallFilteringResult result = new CallFilteringResult.Builder()
                 .setShouldAllowCall(true)
@@ -1477,7 +2066,7 @@
         when(mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(any(), any())).thenReturn(
                 SIM_1_HANDLE);
         when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
-                any(), anyInt(), anyInt())).thenReturn(
+                any(), anyInt(), anyInt(), anyBoolean())).thenReturn(
                 new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
 
         // Let's add an existing call which is in connecting state; this emulates the case where
@@ -1604,7 +2193,35 @@
                 any(DisconnectCause.class));
         verify(callSpy, never()).setDisconnectCause(any(DisconnectCause.class));
     }
-    
+
+    @SmallTest
+    @Test
+    public void testCallStreamingStateChanged() throws Exception {
+        Call call = createCall(SIM_1_HANDLE, CallState.NEW);
+        call.setIsTransactionalCall(true);
+        CountDownLatch streamingStarted = new CountDownLatch(1);
+        CountDownLatch streamingStopped = new CountDownLatch(1);
+        Call.Listener l = new Call.ListenerBase() {
+            @Override
+            public void onCallStreamingStateChanged(Call call, boolean isStreaming) {
+                if (isStreaming) {
+                    streamingStarted.countDown();
+                } else {
+                    streamingStopped.countDown();
+                }
+            }
+        };
+        call.addListener(l);
+
+        // Start call streaming
+        call.startStreaming();
+        assertTrue(streamingStarted.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS));
+
+        // Stop call streaming
+        call.stopStreaming();
+        assertTrue(streamingStopped.await(TEST_TIMEOUT, TimeUnit.MILLISECONDS));
+    }
+
     /**
      * Verifies that if call state goes from DIALING to DISCONNECTED, and a call diagnostic service
      * IS in use, it would call onCallDisconnected of the CallDiagnosticService
@@ -1645,6 +2262,79 @@
                 SELF_MANAGED_HANDLE.getUserHandle()));
     }
 
+    /**
+     * Emulate the case where a new incoming call is created but the connection fails for a known
+     * reason before being added to CallsManager. In this case, the listeners should be notified
+     * properly.
+     */
+    @Test
+    public void testIncomingCallCreatedButNotAddedNotifyListener() {
+        //The call is created and a listener is added:
+        Call incomingCall = createCall(SIM_2_HANDLE, null, CallState.NEW);
+        CallsManager.CallsManagerListener listener = mock(CallsManager.CallsManagerListener.class);
+        mCallsManager.addListener(listener);
+
+        //The connection fails before being added to CallsManager for a known reason:
+        incomingCall.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED));
+
+        //Ensure the listener is notified properly:
+        verify(listener).onCreateConnectionFailed(incomingCall);
+    }
+
+    /**
+     * Emulate the case where a new incoming call is created but the connection fails for a known
+     * reason after being added to CallsManager. Since the call was added to CallsManager, the
+     * listeners should not be notified via onCreateConnectionFailed().
+     */
+    @Test
+    public void testIncomingCallCreatedAndAddedDoNotNotifyListener() {
+        //The call is created and a listener is added:
+        Call incomingCall = createCall(SIM_2_HANDLE, null, CallState.NEW);
+        CallsManager.CallsManagerListener listener = mock(CallsManager.CallsManagerListener.class);
+        mCallsManager.addListener(listener);
+
+        //The call is added to CallsManager:
+        mCallsManager.addCall(incomingCall);
+
+        //The connection fails after being added to CallsManager for a known reason:
+        incomingCall.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED));
+
+        //Since the call was added to CallsManager, onCreateConnectionFailed shouldn't be invoked:
+        verify(listener, never()).onCreateConnectionFailed(incomingCall);
+    }
+
+    /**
+     * Emulate the case where a new outgoing call is created but is aborted before being added to
+     * CallsManager since there are no available phone accounts. In this case, the listeners
+     * should be notified properly.
+     */
+    @Test
+    public void testAbortOutgoingCallNoPhoneAccountsNotifyListeners() throws Exception {
+        // Setup a new outgoing call and add a listener
+        Call newCall = addSpyCall(CallState.NEW);
+        CallsManager.CallsManagerListener listener = mock(CallsManager.CallsManagerListener.class);
+        mCallsManager.addListener(listener);
+
+        // Ensure contact info lookup succeeds but do not set the phone account info
+        doAnswer(invocation -> {
+            Uri handle = invocation.getArgument(0);
+            CallerInfo info = new CallerInfo();
+            CompletableFuture<Pair<Uri, CallerInfo>> callerInfoFuture = new CompletableFuture<>();
+            callerInfoFuture.complete(new Pair<>(handle, info));
+            return callerInfoFuture;
+        }).when(mCallerInfoLookupHelper).startLookup(any(Uri.class));
+
+        // Start the outgoing call
+        CompletableFuture<Call> callFuture = mCallsManager.startOutgoingCall(
+                newCall.getHandle(), newCall.getTargetPhoneAccount(), new Bundle(),
+                UserHandle.CURRENT, new Intent(), "com.test.stuff");
+        Call result = callFuture.get(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
+
+        //Ensure the listener is notified properly:
+        verify(listener).onCreateConnectionFailed(any());
+        assertNull(result);
+    }
+
     @Test
     public void testIsInSelfManagedCallOnlySelfManaged() {
         Call selfManagedCall = createCall(SELF_MANAGED_HANDLE, CallState.ACTIVE);
@@ -1676,6 +2366,43 @@
                 new UserHandle(90210)));
     }
 
+    /**
+     * Verifies that if a {@link android.telecom.CallScreeningService} app can properly request
+     * notification show for rejected calls.
+     */
+    @SmallTest
+    @Test
+    public void testCallScreeningServiceRequestShowNotification() {
+        Call callSpy = addSpyCall(CallState.NEW);
+        CallFilteringResult result = new CallFilteringResult.Builder()
+                .setShouldAllowCall(false)
+                .setShouldReject(true)
+                .setCallScreeningComponentName("com.foo/.Blah")
+                .setCallScreeningAppName("Blah")
+                .setShouldAddToCallLog(true)
+                .setShouldShowNotification(true).build();
+
+        mCallsManager.onCallFilteringComplete(callSpy, result, false /* timeout */);
+        verify(mMissedCallNotifier).showMissedCallNotification(
+                any(MissedCallNotifier.CallInfo.class));
+    }
+
+    @Test
+    public void testSetStateOnlyCalledOnce() {
+        // GIVEN a new self-managed call
+        Call newCall = addSpyCall();
+        doReturn(true).when(newCall).isSelfManaged();
+        newCall.setState(CallState.DISCONNECTED, "");
+
+        // WHEN ActionSetCallState is given a disconnect call
+        assertEquals(CallState.DISCONNECTED, newCall.getState());
+        // attempt to set the call active
+        mCallsManager.createActionSetCallStateAndPerformAction(newCall, CallState.ACTIVE, "");
+
+        // THEN assert remains disconnected
+        assertEquals(CallState.DISCONNECTED, newCall.getState());
+    }
+
     @SmallTest
     @Test
     public void testCrossUserCallRedirectionEndEarlyForIncapablePhoneAccount() {
@@ -1693,6 +2420,775 @@
         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.getAssociatedUser()))
+                .thenReturn(DEFAULT_CALL_SCREENING_APP);
+        assertNull(outgoingCall.getPostCallPackageName());
+        mCallsManager.onSuccessfulOutgoingCall(outgoingCall, CallState.CONNECTING);
+        assertEquals(DEFAULT_CALL_SCREENING_APP, outgoingCall.getPostCallPackageName());
+    }
+
+    @SmallTest
+    @Test
+    public void testRejectIncomingCallOnPAHInactive() throws Exception {
+        ConnectionServiceWrapper service = mock(ConnectionServiceWrapper.class);
+        doReturn(SIM_2_HANDLE.getComponentName()).when(service).getComponentName();
+        mCallsManager.addConnectionServiceRepositoryCache(SIM_2_HANDLE.getComponentName(),
+                SIM_2_HANDLE.getUserHandle(), service);
+
+        UserManager um = mContext.getSystemService(UserManager.class);
+        when(um.isQuietModeEnabled(eq(SIM_2_HANDLE.getUserHandle()))).thenReturn(true);
+        Call newCall = mCallsManager.processIncomingCallIntent(
+                SIM_2_HANDLE, new Bundle(), false);
+
+        verify(service, timeout(TEST_TIMEOUT)).createConnectionFailed(any());
+        assertFalse(newCall.isInECBM());
+        assertEquals(USER_MISSED_NOT_RUNNING, newCall.getMissedReason());
+    }
+
+    @SmallTest
+    @Test
+    public void testAcceptIncomingCallOnPAHInactiveAndECBMActive() throws Exception {
+        ConnectionServiceWrapper service = mock(ConnectionServiceWrapper.class);
+        doReturn(SIM_2_HANDLE.getComponentName()).when(service).getComponentName();
+        mCallsManager.addConnectionServiceRepositoryCache(SIM_2_HANDLE.getComponentName(),
+                SIM_2_HANDLE.getUserHandle(), service);
+
+        when(mEmergencyCallHelper.isLastOutgoingEmergencyCallPAH(eq(SIM_2_HANDLE)))
+                .thenReturn(true);
+        UserManager um = mContext.getSystemService(UserManager.class);
+        when(um.isQuietModeEnabled(eq(SIM_2_HANDLE.getUserHandle()))).thenReturn(true);
+        Call newCall = mCallsManager.processIncomingCallIntent(
+                SIM_2_HANDLE, new Bundle(), false);
+
+        assertTrue(newCall.isInECBM());
+        verify(service, timeout(TEST_TIMEOUT).times(0)).createConnectionFailed(any());
+    }
+
+    @SmallTest
+    @Test
+    public void testAcceptIncomingEmergencyCallOnPAHInactive() throws Exception {
+        ConnectionServiceWrapper service = mock(ConnectionServiceWrapper.class);
+        doReturn(SIM_2_HANDLE.getComponentName()).when(service).getComponentName();
+        mCallsManager.addConnectionServiceRepositoryCache(SIM_2_HANDLE.getComponentName(),
+                SIM_2_HANDLE.getUserHandle(), service);
+
+        Bundle extras = new Bundle();
+        extras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS, TEST_ADDRESS);
+        TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
+        UserManager um = mContext.getSystemService(UserManager.class);
+        when(um.isQuietModeEnabled(eq(SIM_2_HANDLE.getUserHandle()))).thenReturn(true);
+        when(tm.isEmergencyNumber(any(String.class))).thenReturn(true);
+        Call newCall = mCallsManager.processIncomingCallIntent(
+                SIM_2_HANDLE, extras, false);
+
+        assertFalse(newCall.isInECBM());
+        assertTrue(newCall.isEmergencyCall());
+        verify(service, timeout(TEST_TIMEOUT).times(0)).createConnectionFailed(any());
+    }
+
+    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));
+    }
+
+    /**
+     * Verify CallsManager#isInSelfManagedCall(packageName, userHandle) returns true when
+     * CallsManager is first made aware of the incoming call in processIncomingCallIntent.
+     */
+    @SmallTest
+    @Test
+    public void testAddNewIncomingCall_IsInSelfManagedCall() {
+        // GIVEN
+        assertEquals(0, mCallsManager.getSelfManagedCallsBeingSetup().size());
+        assertFalse(mCallsManager.isInSelfManagedCall(TEST_PACKAGE_NAME, TEST_USER_HANDLE));
+
+        // WHEN
+        when(mPhoneAccountRegistrar.getPhoneAccountUnchecked(any()))
+                .thenReturn(SM_W_DIFFERENT_PACKAGE_AND_USER);
+
+        // THEN
+        mCallsManager.processIncomingCallIntent(SELF_MANAGED_W_CUSTOM_HANDLE, new Bundle(), false);
+
+        assertEquals(1, mCallsManager.getSelfManagedCallsBeingSetup().size());
+        assertTrue(mCallsManager.isInSelfManagedCall(TEST_PACKAGE_NAME, TEST_USER_HANDLE));
+        assertEquals(0, mCallsManager.getCalls().size());
+    }
+
+    /**
+     * Verify CallsManager#isInSelfManagedCall(packageName, userHandle) returns true when
+     * CallsManager is first made aware of the outgoing call in StartOutgoingCall.
+     */
+    @SmallTest
+    @Test
+    public void testStartOutgoing_IsInSelfManagedCall() {
+        // GIVEN
+        assertEquals(0, mCallsManager.getSelfManagedCallsBeingSetup().size());
+        assertFalse(mCallsManager.isInSelfManagedCall(TEST_PACKAGE_NAME, TEST_USER_HANDLE));
+
+        // WHEN
+        when(mPhoneAccountRegistrar.getPhoneAccount(any(), any()))
+                .thenReturn(SM_W_DIFFERENT_PACKAGE_AND_USER);
+        // 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(
+                SELF_MANAGED_W_CUSTOM_HANDLE);
+        when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
+                any(), anyInt(), anyInt(), anyBoolean())).thenReturn(
+                new ArrayList<>(List.of(SELF_MANAGED_W_CUSTOM_HANDLE)));
+
+        // THEN
+        mCallsManager.startOutgoingCall(TEST_ADDRESS, SELF_MANAGED_W_CUSTOM_HANDLE, new Bundle(),
+                TEST_USER_HANDLE, new Intent(), TEST_PACKAGE_NAME);
+
+        assertEquals(1, mCallsManager.getSelfManagedCallsBeingSetup().size());
+        assertTrue(mCallsManager.isInSelfManagedCall(TEST_PACKAGE_NAME, TEST_USER_HANDLE));
+        assertEquals(0, mCallsManager.getCalls().size());
+    }
+
+    /**
+     * Verify SelfManagedCallsBeingSetup is being cleaned up in CallsManager#addCall and
+     * CallsManager#removeCall.  This ensures no memory leaks.
+     */
+    @SmallTest
+    @Test
+    public void testCallsBeingSetupCleanup() {
+        Call spyCall = addSpyCall();
+        assertEquals(0, mCallsManager.getSelfManagedCallsBeingSetup().size());
+        // verify CallsManager#removeCall removes the call from SelfManagedCallsBeingSetup
+        mCallsManager.addCallBeingSetup(spyCall);
+        mCallsManager.removeCall(spyCall);
+        assertEquals(0, mCallsManager.getSelfManagedCallsBeingSetup().size());
+        // verify CallsManager#addCall removes the call from SelfManagedCallsBeingSetup
+        mCallsManager.addCallBeingSetup(spyCall);
+        mCallsManager.addCall(spyCall);
+        assertEquals(0, mCallsManager.getSelfManagedCallsBeingSetup().size());
+    }
+
+    /**
+     * Verify isInSelfManagedCall returns false if there is a self-managed call, but it is for a
+     * different package and user
+     */
+    @SmallTest
+    @Test
+    public void testIsInSelfManagedCall_PackageUserQueryIsWorkingAsIntended() {
+        // start an active call
+        Call randomCall = createSpyCall(SELF_MANAGED_HANDLE, CallState.ACTIVE);
+        mCallsManager.addCallBeingSetup(randomCall);
+        assertEquals(1, mCallsManager.getSelfManagedCallsBeingSetup().size());
+        // query isInSelfManagedCall for a package that is NOT in a call;  expect false
+        assertFalse(mCallsManager.isInSelfManagedCall(TEST_PACKAGE_NAME, TEST_USER_HANDLE));
+        // start another call
+        Call targetCall = addSpyCall(SELF_MANAGED_W_CUSTOM_HANDLE, CallState.DIALING);
+        when(targetCall.getTargetPhoneAccount()).thenReturn(SELF_MANAGED_W_CUSTOM_HANDLE);
+        when(targetCall.isSelfManaged()).thenReturn(true);
+        mCallsManager.addCallBeingSetup(targetCall);
+        // query isInSelfManagedCall for a package that is in a call
+        assertTrue(mCallsManager.isInSelfManagedCall(TEST_PACKAGE_NAME, TEST_USER_HANDLE));
+    }
+
+
     private Call addSpyCall() {
         return addSpyCall(SIM_2_HANDLE, CallState.ACTIVE);
     }
@@ -1765,6 +3261,10 @@
                 mClockProxy,
                 mToastFactory);
         ongoingCall.setState(initialState, "just cuz");
+        if (targetPhoneAccount == SELF_MANAGED_HANDLE
+                || targetPhoneAccount == SELF_MANAGED_2_HANDLE) {
+            ongoingCall.setIsSelfManaged(true);
+        }
         return ongoingCall;
     }
 
@@ -1780,9 +3280,15 @@
         TelephonyManager mockTelephonyManager = mComponentContextFixture.getTelephonyManager();
         when(mockTelephonyManager.getMaxNumberOfSimultaneouslyActiveSims()).thenReturn(1);
         when(mPhoneAccountRegistrar.getCallCapablePhoneAccounts(any(), anyBoolean(),
-                any(), anyInt(), anyInt())).thenReturn(
+                any(), anyInt(), anyInt(), anyBoolean())).thenReturn(
                 new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
         when(mPhoneAccountRegistrar.getSimPhoneAccountsOfCurrentUser()).thenReturn(
                 new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
     }
+
+    private void setMaxActiveVoiceSubscriptions(int num) {
+        TelephonyManager mockTelephonyManager = mComponentContextFixture.getTelephonyManager();
+        when(mockTelephonyManager.getPhoneCapability()).thenReturn(mPhoneCapability);
+        when(mPhoneCapability.getMaxActiveVoiceSubscriptions()).thenReturn(num);
+    }
 }
diff --git a/tests/src/com/android/server/telecom/tests/CarModeTrackerTest.java b/tests/src/com/android/server/telecom/tests/CarModeTrackerTest.java
index 4ad46ae..6056747 100644
--- a/tests/src/com/android/server/telecom/tests/CarModeTrackerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CarModeTrackerTest.java
@@ -236,6 +236,28 @@
     }
 
     /**
+     * Verifies that setting automotive projection overrides entering car mode with the highest
+     * priority of 0. Also ensures exiting car mode doesn't interfere with the automotive
+     * projection being set.
+     */
+    @Test
+    public void testInterleaveCarModeAndProjectionMode() {
+        mCarModeTracker.handleEnterCarMode(0, CAR_MODE_APP1_PACKAGE_NAME);
+        assertEquals(CAR_MODE_APP1_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage());
+        assertTrue(mCarModeTracker.isInCarMode());
+
+        mCarModeTracker.handleSetAutomotiveProjection(CAR_MODE_APP2_PACKAGE_NAME);
+        assertEquals(CAR_MODE_APP2_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage());
+        assertTrue(mCarModeTracker.isInCarMode());
+
+        mCarModeTracker.handleExitCarMode(0, CAR_MODE_APP1_PACKAGE_NAME);
+        assertEquals(CAR_MODE_APP2_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage());
+        assertTrue(mCarModeTracker.isInCarMode());
+
+        mCarModeTracker.handleReleaseAutomotiveProjection();
+    }
+
+    /**
      * Verifies that if we set automotive projection more than once with the same package, nothing
      * changes.
      */
diff --git a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
index 477ca9f..cc22de2 100644
--- a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
@@ -52,11 +52,16 @@
 import android.content.res.Resources;
 import android.hardware.SensorPrivacyManager;
 import android.location.CountryDetector;
+import android.location.LocationManager;
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
+import android.os.BugreportManager;
 import android.os.Bundle;
+import android.os.DropBoxManager;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.IInterface;
+import android.os.Looper;
 import android.os.PersistableBundle;
 import android.os.Process;
 import android.os.UserHandle;
@@ -74,6 +79,7 @@
 import android.telephony.TelephonyRegistryManager;
 import android.test.mock.MockContext;
 import android.util.DisplayMetrics;
+import android.view.accessibility.AccessibilityManager;
 
 import java.io.File;
 import java.io.IOException;
@@ -85,6 +91,8 @@
 import java.util.Map;
 import java.util.concurrent.Executor;
 
+import static android.content.Context.DEVICE_ID_DEFAULT;
+
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.matches;
 import static org.mockito.ArgumentMatchers.nullable;
@@ -106,6 +114,7 @@
  * property points to an application context implementing all the nontrivial functionality.
  */
 public class ComponentContextFixture implements TestFixture<Context> {
+    private HandlerThread mHandlerThread;
 
     public class FakeApplicationContext extends MockContext {
         @Override
@@ -126,6 +135,9 @@
         }
 
         @Override
+        public Context createAttributionContext(String attributionTag) { return this; }
+
+        @Override
         public String getPackageName() {
             return "com.android.server.telecom.tests";
         }
@@ -199,6 +211,8 @@
                     return mAudioManager;
                 case Context.TELEPHONY_SERVICE:
                     return mTelephonyManager;
+                case Context.LOCATION_SERVICE:
+                    return mLocationManager;
                 case Context.APP_OPS_SERVICE:
                     return mAppOpsManager;
                 case Context.NOTIFICATION_SERVICE:
@@ -229,6 +243,8 @@
                     return mPermissionCheckerManager;
                 case Context.SENSOR_PRIVACY_SERVICE:
                     return mSensorPrivacyManager;
+                case Context.ACCESSIBILITY_SERVICE:
+                    return mAccessibilityManager;
                 default:
                     return null;
             }
@@ -262,8 +278,14 @@
                 return Context.SENSOR_PRIVACY_SERVICE;
             } else if (svcClass == NotificationManager.class) {
                 return Context.NOTIFICATION_SERVICE;
+            } else if (svcClass == AccessibilityManager.class) {
+                return Context.ACCESSIBILITY_SERVICE;
+            } else if (svcClass == DropBoxManager.class) {
+                return Context.DROPBOX_SERVICE;
+            } else if (svcClass == BugreportManager.class) {
+                return Context.BUGREPORT_SERVICE;
             }
-            throw new UnsupportedOperationException();
+            throw new UnsupportedOperationException(svcClass.getName());
         }
 
         @Override
@@ -292,6 +314,15 @@
         }
 
         @Override
+        public Looper getMainLooper() {
+            if (mHandlerThread == null) {
+                mHandlerThread = new HandlerThread(this.getClass().getSimpleName());
+                mHandlerThread.start();
+            }
+            return mHandlerThread.getLooper();
+        }
+
+        @Override
         public ContentResolver getContentResolver() {
             return new ContentResolver(mApplicationContextSpy) {
                 @Override
@@ -331,30 +362,34 @@
 
         @Override
         public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
-            // TODO -- this is called by WiredHeadsetManager!!!
+            mBroadcastReceivers.add(receiver);
             return null;
         }
 
         @Override
         public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, int flags) {
+            mBroadcastReceivers.add(receiver);
             return null;
         }
 
         @Override
         public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
                 String broadcastPermission, Handler scheduler) {
+            mBroadcastReceivers.add(receiver);
             return null;
         }
 
         @Override
         public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
                 String broadcastPermission, Handler scheduler, int flags) {
+            mBroadcastReceivers.add(receiver);
             return null;
         }
 
         @Override
         public Intent registerReceiverAsUser(BroadcastReceiver receiver, UserHandle handle,
                 IntentFilter filter, String broadcastPermission, Handler scheduler) {
+            mBroadcastReceivers.add(receiver);
             return null;
         }
 
@@ -534,6 +569,11 @@
         public Resources getResources() {
             return mResources;
         }
+
+        @Override
+        public int getDeviceId() {
+          return DEVICE_ID_DEFAULT;
+        }
     };
 
     // The application context is the most important object this class provides to the system
@@ -551,8 +591,10 @@
     private final Executor mMainExecutor = mock(Executor.class);
     private final AudioManager mAudioManager = spy(new FakeAudioManager(mContext));
     private final TelephonyManager mTelephonyManager = mock(TelephonyManager.class);
+    private final LocationManager mLocationManager = mock(LocationManager.class);
     private final AppOpsManager mAppOpsManager = mock(AppOpsManager.class);
     private final NotificationManager mNotificationManager = mock(NotificationManager.class);
+    private final AccessibilityManager mAccessibilityManager = mock(AccessibilityManager.class);
     private final UserManager mUserManager = mock(UserManager.class);
     private final StatusBarManager mStatusBarManager = mock(StatusBarManager.class);
     private SubscriptionManager mSubscriptionManager = mock(SubscriptionManager.class);
@@ -571,6 +613,7 @@
             mock(PermissionCheckerManager.class);
     private final PermissionInfo mPermissionInfo = mock(PermissionInfo.class);
     private final SensorPrivacyManager mSensorPrivacyManager = mock(SensorPrivacyManager.class);
+    private final List<BroadcastReceiver> mBroadcastReceivers = new ArrayList<>();
 
     private TelecomManager mTelecomManager = mock(TelecomManager.class);
 
@@ -763,6 +806,10 @@
         return mTelephonyManager;
     }
 
+    public AudioManager getAudioManager() {
+        return mAudioManager;
+    }
+
     public CarrierConfigManager getCarrierConfigManager() {
         return mCarrierConfigManager;
     }
@@ -771,6 +818,10 @@
         return mNotificationManager;
     }
 
+    public List<BroadcastReceiver> getBroadcastReceivers() {
+        return mBroadcastReceivers;
+    }
+
     private void addService(String action, ComponentName name, IInterface service) {
         mComponentNamesByAction.put(action, name);
         mServiceByComponentName.put(name, service);
diff --git a/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java b/tests/src/com/android/server/telecom/tests/ConnectionServiceFixture.java
index 6e6646f..0927b80 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;
@@ -68,6 +69,9 @@
     static int INVALID_VIDEO_STATE = -1;
     public CountDownLatch mExtrasLock = new CountDownLatch(1);
     static int NOT_SPECIFIED = 0;
+    public static final String STATUS_HINTS_EXTRA = "updateStatusHints";
+    public static final PhoneAccountHandle TEST_PHONE_ACCOUNT_HANDLE =
+            new PhoneAccountHandle(new ComponentName("pkg", "cls"), "test");
 
     /**
      * Implementation of ConnectionService that performs no-ops for tasks normally meant for
@@ -102,6 +106,11 @@
             if (mProperties != NOT_SPECIFIED) {
                 fakeConnection.setConnectionProperties(mProperties);
             }
+            // Testing for StatusHints image icon cross user access
+            if (request.getExtras() != null) {
+                fakeConnection.setStatusHints(
+                        request.getExtras().getParcelable(STATUS_HINTS_EXTRA));
+            }
 
             return fakeConnection;
         }
@@ -118,6 +127,11 @@
             if (mProperties != NOT_SPECIFIED) {
                 fakeConnection.setConnectionProperties(mProperties);
             }
+            // Testing for StatusHints image icon cross user access
+            if (request.getExtras() != null) {
+                fakeConnection.setStatusHints(
+                        request.getExtras().getParcelable(STATUS_HINTS_EXTRA));
+            }
             return fakeConnection;
         }
 
@@ -134,6 +148,12 @@
                 Conference fakeConference = new FakeConference();
                 fakeConference.addConnection(cxn1);
                 fakeConference.addConnection(cxn2);
+                if (cxn1.getStatusHints() != null || cxn2.getStatusHints() != null) {
+                    // For testing purposes, pick one of the status hints that isn't null.
+                    StatusHints statusHints = cxn1.getStatusHints() != null
+                            ? cxn1.getStatusHints() : cxn2.getStatusHints();
+                    fakeConference.setStatusHints(statusHints);
+                }
                 mLatestConference = fakeConference;
                 addConference(fakeConference);
             } else {
@@ -178,7 +198,7 @@
 
     public class FakeConference extends Conference {
         public FakeConference() {
-            super(null);
+            super(TEST_PHONE_ACCOUNT_HANDLE);
             setConnectionCapabilities(
                     Connection.CAPABILITY_SUPPORT_HOLD
                             | Connection.CAPABILITY_HOLD
@@ -343,6 +363,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 { }
 
@@ -494,6 +526,7 @@
 
     public String mLatestConnectionId;
     public Connection mLatestConnection;
+    public ParcelableConnection mLatestParcelableConnection;
     public Conference mLatestConference;
     public final Set<IConnectionServiceAdapter> mConnectionServiceAdapters = new HashSet<>();
     public final Map<String, ConnectionInfo> mConnectionById = new HashMap<>();
@@ -738,7 +771,7 @@
     }
 
     private ParcelableConnection parcelable(ConnectionInfo c) {
-        return new ParcelableConnection(
+        mLatestParcelableConnection = new ParcelableConnection(
                 c.request.getAccountHandle(),
                 c.state,
                 c.capabilities,
@@ -759,5 +792,6 @@
                 c.conferenceableConnectionIds,
                 c.extras,
                 c.callerNumberVerificationStatus);
+        return mLatestParcelableConnection;
     }
 }
diff --git a/tests/src/com/android/server/telecom/tests/CreateConnectionProcessorTest.java b/tests/src/com/android/server/telecom/tests/CreateConnectionProcessorTest.java
index cb376af..8a85a87 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.getAssociatedUser()).
+                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());
@@ -479,6 +517,43 @@
     }
 
     /**
+     * Ensure that the call goes out on the PhoneAccount for the incoming call and not the
+     * Telephony preferred emergency account.
+     */
+    @SmallTest
+    @Test
+    public void testMTEmergencyCallMultiSimUserPreferred() throws Exception {
+        when(mMockCall.isEmergencyCall()).thenReturn(true);
+        when(mMockCall.isTestEmergencyCall()).thenReturn(false);
+        when(mMockCall.isIncoming()).thenReturn(true);
+        ConnectionServiceWrapper service = makeConnectionServiceWrapper();
+        PhoneAccount emergencyPhoneAccount1 = makeEmergencyPhoneAccount("tel_emer1", 0, null);
+        mapToSubSlot(emergencyPhoneAccount1, 1 /*subId*/, 0 /*slotId*/);
+        setTargetPhoneAccount(mMockCall, emergencyPhoneAccount1.getAccountHandle());
+        phoneAccounts.add(emergencyPhoneAccount1);
+        // Make this the user preferred phone account
+        setTargetPhoneAccount(mMockCall, emergencyPhoneAccount1.getAccountHandle());
+        PhoneAccount emergencyPhoneAccount2 = makeEmergencyPhoneAccount("tel_emer2",
+                PhoneAccount.CAPABILITY_EMERGENCY_PREFERRED, null);
+        mapToSubSlot(emergencyPhoneAccount2, 2 /*subId*/, 1 /*slotId*/);
+        phoneAccounts.add(emergencyPhoneAccount2);
+        PhoneAccountHandle emergencyPhoneAccountHandle2 = emergencyPhoneAccount2.getAccountHandle();
+
+        mTestCreateConnectionProcessor.process();
+
+        verify(mMockCall).setConnectionManagerPhoneAccount(
+                eq(emergencyPhoneAccount1.getAccountHandle()));
+        // The account we're using to place the call should be the user preferred account
+        verify(mMockCall).setTargetPhoneAccount(eq(emergencyPhoneAccount1.getAccountHandle()));
+        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);
+    }
+
+    /**
      * If the user preferred PhoneAccount is associated with an invalid slot, place on the other,
      * valid slot.
      */
@@ -492,13 +567,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 +604,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 +668,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 +757,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 +765,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 +778,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 +796,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 +804,7 @@
     }
 
     private PhoneAccountHandle getNewTargetPhoneAccountHandle(String id) {
-        PhoneAccountHandle pAHandle = makeQuickAccountHandle(id);
+        PhoneAccountHandle pAHandle = makeQuickAccountHandle(id, null);
         givePhoneAccountBindPermission(pAHandle);
         return pAHandle;
     }
@@ -739,7 +815,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 +825,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 +842,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/DisconnectedCallNotifierTest.java b/tests/src/com/android/server/telecom/tests/DisconnectedCallNotifierTest.java
index 2cdc23a..05c5071 100644
--- a/tests/src/com/android/server/telecom/tests/DisconnectedCallNotifierTest.java
+++ b/tests/src/com/android/server/telecom/tests/DisconnectedCallNotifierTest.java
@@ -151,6 +151,7 @@
         when(call.getDisconnectCause()).thenReturn(cause);
         when(call.getTargetPhoneAccount()).thenReturn(PHONE_ACCOUNT_HANDLE);
         when(call.getHandle()).thenReturn(TEL_CALL_HANDLE);
+        when(call.getAssociatedUser()).thenReturn(PHONE_ACCOUNT_HANDLE.getUserHandle());
         return call;
     }
 }
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..692d720
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/EmergencyCallHelperTest.java
@@ -0,0 +1,271 @@
+/*
+ * 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.ComponentName;
+import android.content.ContentResolver;
+import android.content.pm.PackageManager;
+import android.os.UserHandle;
+import android.telecom.PhoneAccountHandle;
+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.Mock;
+import org.mockito.MockitoAnnotations;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doThrow;
+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 static org.mockito.Mockito.any;
+
+@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;
+  @Mock private PhoneAccountHandle mPhoneAccountHandle;
+
+  @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);
+    when(mTimeoutsAdapter.getEmergencyCallbackWindowMillis(any(ContentResolver.class))).thenReturn(
+            5000L);
+  }
+
+  @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);
+  }
+
+  @SmallTest
+  @Test
+  public void testIsLastOutgoingEmergencyCallPAH() {
+    PhoneAccountHandle dummyHandle = new PhoneAccountHandle(new ComponentName("pkg", "cls"), "foo");
+    long currentTimeMillis = System.currentTimeMillis();
+    mEmergencyCallHelper.setLastOutgoingEmergencyCallPAH(mPhoneAccountHandle);
+    mEmergencyCallHelper.setLastOutgoingEmergencyCallTimestampMillis(currentTimeMillis);
+
+    // Verify that ECBM is active on mPhoneAccountHandle.
+    assertTrue(mEmergencyCallHelper.isLastOutgoingEmergencyCallPAH(mPhoneAccountHandle));
+    assertFalse(mEmergencyCallHelper.isLastOutgoingEmergencyCallPAH(dummyHandle));
+
+    // Expire ECBM and verify that mPhoneAccountHandle is no longer supported for ECBM.
+    mEmergencyCallHelper.setLastOutgoingEmergencyCallTimestampMillis(currentTimeMillis/2);
+    assertFalse(mEmergencyCallHelper.isLastOutgoingEmergencyCallPAH(mPhoneAccountHandle));
+  }
+}
diff --git a/tests/src/com/android/server/telecom/tests/HeadsetMediaButtonTest.java b/tests/src/com/android/server/telecom/tests/HeadsetMediaButtonTest.java
index 6d15e60..ce23724 100644
--- a/tests/src/com/android/server/telecom/tests/HeadsetMediaButtonTest.java
+++ b/tests/src/com/android/server/telecom/tests/HeadsetMediaButtonTest.java
@@ -16,20 +16,34 @@
 
 package com.android.server.telecom.tests;
 
+import android.content.Intent;
+import android.media.session.MediaSession;
+import android.telecom.CallEndpoint;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.KeyEvent;
+
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.HeadsetMediaButton;
 import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.HeadsetMediaButton.MediaSessionWrapper;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -38,6 +52,7 @@
     private static final int TEST_TIMEOUT_MILLIS = 1000;
 
     private HeadsetMediaButton mHeadsetMediaButton;
+    private MediaSession.Callback mSessionCallback;
 
     @Mock private CallsManager mMockCallsManager;
     @Mock private HeadsetMediaButton.MediaSessionAdapter mMediaSessionAdapter;
@@ -47,8 +62,15 @@
     @Before
     public void setUp() throws Exception {
         super.setUp();
+
+        ArgumentCaptor<MediaSession.Callback> sessionCallbackArgument =
+                ArgumentCaptor.forClass(MediaSession.Callback.class);
+
         mHeadsetMediaButton = new HeadsetMediaButton(mContext, mMockCallsManager, mLock,
                 mMediaSessionAdapter);
+
+        verify(mMediaSessionAdapter).setCallback(sessionCallbackArgument.capture());
+        mSessionCallback = sessionCallbackArgument.getValue();
     }
 
     @Override
@@ -59,8 +81,9 @@
     }
 
     /**
-     * Nominal case; just add a call and remove it.
+     * Nominal case; just add a call and remove it; this happens when the audio state is earpiece.
      */
+    @SmallTest
     @Test
     public void testAddCall() {
         Call regularCall = getRegularCall();
@@ -68,19 +91,101 @@
         when(mMockCallsManager.hasAnyCalls()).thenReturn(true);
         mHeadsetMediaButton.onCallAdded(regularCall);
         waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+        verify(mMediaSessionAdapter, never()).setActive(eq(true));
+
+        // Report that the endpoint is earpiece and other routes that don't matter
+        mHeadsetMediaButton.onCallEndpointChanged(
+                new CallEndpoint("Earpiece", CallEndpoint.TYPE_EARPIECE));
+        mHeadsetMediaButton.onCallEndpointChanged(
+                new CallEndpoint("Speaker", CallEndpoint.TYPE_SPEAKER));
+        mHeadsetMediaButton.onCallEndpointChanged(
+                new CallEndpoint("BT", CallEndpoint.TYPE_BLUETOOTH));
+        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+        verify(mMediaSessionAdapter, never()).setActive(eq(true));
+
+        // ... and thus we see how the original code isn't amenable to tests.
+        when(mMediaSessionAdapter.isActive()).thenReturn(false);
+
+        // Still should not have done anything; we never hit wired headset
+        mHeadsetMediaButton.onCallRemoved(regularCall);
+        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+        verify(mMediaSessionAdapter, never()).setActive(eq(false));
+    }
+
+    /**
+     * Call is added and then routed to headset after call start
+     */
+    @SmallTest
+    @Test
+    public void testAddCallThenRouteToHeadset() {
+        Call regularCall = getRegularCall();
+
+        mHeadsetMediaButton.onCallAdded(regularCall);
+        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+        verify(mMediaSessionAdapter, never()).setActive(eq(true));
+
+        mHeadsetMediaButton.onCallEndpointChanged(
+                new CallEndpoint("Wired Headset", CallEndpoint.TYPE_WIRED_HEADSET));
+        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
         verify(mMediaSessionAdapter).setActive(eq(true));
+
         // ... and thus we see how the original code isn't amenable to tests.
         when(mMediaSessionAdapter.isActive()).thenReturn(true);
 
-        when(mMockCallsManager.hasAnyCalls()).thenReturn(false);
+        // Remove the one call; we should release the session.
         mHeadsetMediaButton.onCallRemoved(regularCall);
         waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
         verify(mMediaSessionAdapter).setActive(eq(false));
+        when(mMediaSessionAdapter.isActive()).thenReturn(false);
+
+        // Add a new call; make sure we go active once more.
+        mHeadsetMediaButton.onCallAdded(regularCall);
+        mHeadsetMediaButton.onCallEndpointChanged(
+                new CallEndpoint("Wired Headset", CallEndpoint.TYPE_WIRED_HEADSET));
+        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+        verify(mMediaSessionAdapter, times(2)).setActive(eq(true));
+    }
+
+    /**
+     * Call is added and then routed to headset after call start
+     */
+    @SmallTest
+    @Test
+    public void testAddCallThenRouteToHeadsetAndBack() {
+        Call regularCall = getRegularCall();
+
+        when(mMockCallsManager.hasAnyCalls()).thenReturn(true);
+        mHeadsetMediaButton.onCallAdded(regularCall);
+        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+        verify(mMediaSessionAdapter, never()).setActive(eq(true));
+
+        mHeadsetMediaButton.onCallEndpointChanged(
+                new CallEndpoint("Wired Headset", CallEndpoint.TYPE_WIRED_HEADSET));
+        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+        verify(mMediaSessionAdapter).setActive(eq(true));
+
+        // ... and thus we see how the original code isn't amenable to tests.
+        when(mMediaSessionAdapter.isActive()).thenReturn(true);
+
+        mHeadsetMediaButton.onCallEndpointChanged(
+                new CallEndpoint("Earpiece", CallEndpoint.TYPE_EARPIECE));
+        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+        verify(mMediaSessionAdapter).setActive(eq(false));
+        when(mMediaSessionAdapter.isActive()).thenReturn(false);
+
+        // Remove the one call; we should not release again.
+        mHeadsetMediaButton.onCallRemoved(regularCall);
+        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+        // Remember, mockito counts total invocations; we should have went active once and then
+        // inactive again when we hit earpiece.
+        verify(mMediaSessionAdapter, times(1)).setActive(eq(true));
+        verify(mMediaSessionAdapter, times(1)).setActive(eq(false));
     }
 
     /**
      * Test a case where a regular call becomes an external call, and back again.
      */
+    @SmallTest
     @Test
     public void testRegularCallThatBecomesExternal() {
         Call regularCall = getRegularCall();
@@ -88,6 +193,8 @@
         // Start with a regular old call.
         when(mMockCallsManager.hasAnyCalls()).thenReturn(true);
         mHeadsetMediaButton.onCallAdded(regularCall);
+        mHeadsetMediaButton.onCallEndpointChanged(
+                new CallEndpoint("Wired Headset", CallEndpoint.TYPE_WIRED_HEADSET));
         waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
         verify(mMediaSessionAdapter).setActive(eq(true));
         when(mMediaSessionAdapter.isActive()).thenReturn(true);
@@ -99,6 +206,7 @@
         // Expect to set session inactive.
         waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
         verify(mMediaSessionAdapter).setActive(eq(false));
+        when(mMediaSessionAdapter.isActive()).thenReturn(false);
 
         // For good measure lets make it non-external again.
         when(regularCall.isExternalCall()).thenReturn(false);
@@ -106,7 +214,88 @@
         mHeadsetMediaButton.onExternalCallChanged(regularCall, false);
         // Expect to set session active.
         waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
-        verify(mMediaSessionAdapter).setActive(eq(true));
+        verify(mMediaSessionAdapter, times(2)).setActive(eq(true));
+    }
+
+    @MediumTest
+    @Test
+    public void testExternalCallNotChangesState() {
+        Call externalCall = getRegularCall();
+        when(externalCall.isExternalCall()).thenReturn(true);
+
+        mHeadsetMediaButton.onCallAdded(externalCall);
+        mHeadsetMediaButton.onCallEndpointChanged(
+                new CallEndpoint("Wired Headset", CallEndpoint.TYPE_WIRED_HEADSET));
+        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+        verify(mMediaSessionAdapter, never()).setActive(eq(true));
+
+        mHeadsetMediaButton.onCallRemoved(externalCall);
+        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+        verify(mMediaSessionAdapter, never()).setActive(eq(false));
+    }
+
+    @SmallTest
+    @Test
+    public void testCallbackReceivesKeyEventUnaware() {
+        mSessionCallback.onMediaButtonEvent(getKeyEventIntent(
+                KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_0, false));
+        verify(mMockCallsManager, never()).onMediaButton(anyInt());
+    }
+
+    @SmallTest
+    @Test
+    public void testCallbackReceivesKeyEventShortClick() {
+        mSessionCallback.onMediaButtonEvent(getKeyEventIntent(
+                KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_HEADSETHOOK, false));
+        verify(mMockCallsManager, never()).onMediaButton(anyInt());
+
+        mSessionCallback.onMediaButtonEvent(getKeyEventIntent(
+                KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HEADSETHOOK, false));
+        verify(mMockCallsManager, times(1)).onMediaButton(HeadsetMediaButton.SHORT_PRESS);
+    }
+
+    @SmallTest
+    @Test
+    public void testCallbackReceivesKeyEventLongClick() {
+        mSessionCallback.onMediaButtonEvent(getKeyEventIntent(
+                KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_HEADSETHOOK, true));
+        verify(mMockCallsManager, times(1)).onMediaButton(HeadsetMediaButton.LONG_PRESS);
+
+        mSessionCallback.onMediaButtonEvent(getKeyEventIntent(
+                KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HEADSETHOOK, false));
+        verify(mMockCallsManager, times(1)).onMediaButton(HeadsetMediaButton.LONG_PRESS);
+    }
+
+    @SmallTest
+    @Test
+    public void testMediaSessionWrapperSetActive() {
+        MediaSession session = Mockito.mock(MediaSession.class);
+        MediaSessionWrapper wrapper = mHeadsetMediaButton.new MediaSessionWrapper(session);
+
+        final boolean active = true;
+        wrapper.setActive(active);
+        verify(session).setActive(active);
+    }
+
+    @SmallTest
+    @Test
+    public void testMediaSessionWrapperSetCallback() {
+        MediaSession session = Mockito.mock(MediaSession.class);
+        MediaSessionWrapper wrapper = mHeadsetMediaButton.new MediaSessionWrapper(session);
+
+        wrapper.setCallback(mSessionCallback);
+        verify(session).setCallback(mSessionCallback);
+    }
+
+    @SmallTest
+    @Test
+    public void testMediaSessionWrapperIsActive() {
+        MediaSession session = Mockito.mock(MediaSession.class);
+        MediaSessionWrapper wrapper = mHeadsetMediaButton.new MediaSessionWrapper(session);
+
+        final boolean active = true;
+        when(session.isActive()).thenReturn(active);
+        assertEquals(active, wrapper.isActive());
     }
 
     /**
@@ -117,4 +306,15 @@
         when(regularCall.isExternalCall()).thenReturn(false);
         return regularCall;
     }
+
+    private Intent getKeyEventIntent(int action, int code, boolean longPress) {
+        KeyEvent e = new KeyEvent(action, code);
+        if (longPress) {
+            e = KeyEvent.changeFlags(e, KeyEvent.FLAG_LONG_PRESS);
+        }
+
+        Intent intent = new Intent();
+        intent.putExtra(Intent.EXTRA_KEY_EVENT, e);
+        return intent;
+    }
 }
diff --git a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
index 3d387a8..2f3e4bf 100644
--- a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
+++ b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
@@ -22,11 +22,13 @@
 
 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.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyObject;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.matches;
 import static org.mockito.ArgumentMatchers.nullable;
@@ -34,6 +36,7 @@
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -52,10 +55,12 @@
 import android.app.UiModeManager;
 import android.content.AttributionSource;
 import android.content.AttributionSourceState;
+import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.PermissionChecker;
 import android.content.ServiceConnection;
 import android.content.pm.ApplicationInfo;
@@ -63,9 +68,10 @@
 import android.content.pm.PermissionInfo;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
+import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.compat.testing.PlatformCompatChangeRule;
-import android.os.Binder;
+import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
@@ -73,6 +79,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 +95,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 +103,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,20 +121,20 @@
 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;
 import org.mockito.quality.Strictness;
 import org.mockito.stubbing.Answer;
 
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
 
-import libcore.junit.util.compat.CoreCompatChangeRule;
-
 @RunWith(JUnit4.class)
 public class InCallControllerTests extends TelecomTestCase {
     @Mock CallsManager mMockCallsManager;
@@ -144,35 +153,42 @@
     @Mock Analytics.CallInfoImpl mCallInfo;
     @Mock NotificationManager mNotificationManager;
     @Mock PermissionInfo mMockPermissionInfo;
+    @Mock InCallController.InCallServiceInfo mInCallServiceInfo;
+    @Mock private AnomalyReporterAdapter mAnomalyReporterAdapter;
+    @Mock UserManager mMockUserManager;
+    @Mock UserInfo mMockUserInfo;
+    @Mock UserInfo mMockChildUserInfo; //work profile
 
     @Rule
     public TestRule compatChangeRule = new PlatformCompatChangeRule();
 
-    private static final int CURRENT_USER_ID = 900973;
+    private static final int CURRENT_USER_ID = 9;
     private static final String DEF_PKG = "defpkg";
     private static final String DEF_CLASS = "defcls";
-    private static final int DEF_UID = 1;
+    private static final int DEF_UID = 900972;
     private static final String SYS_PKG = "syspkg";
     private static final String SYS_CLASS = "syscls";
-    private static final int SYS_UID = 2;
+    private static final int SYS_UID = 900971;
     private static final String COMPANION_PKG = "cpnpkg";
     private static final String COMPANION_CLASS = "cpncls";
-    private static final int COMPANION_UID = 3;
+    private static final int COMPANION_UID = 900970;
     private static final String CAR_PKG = "carpkg";
     private static final String CAR2_PKG = "carpkg2";
     private static final String CAR_CLASS = "carcls";
     private static final String CAR2_CLASS = "carcls";
-    private static final int CAR_UID = 4;
-    private static final int CAR2_UID = 5;
+    private static final int CAR_UID = 900969;
+    private static final int CAR2_UID = 900968;
     private static final String NONUI_PKG = "nonui_pkg";
     private static final String NONUI_CLASS = "nonui_cls";
-    private static final int NONUI_UID = 6;
+    private static final int NONUI_UID = 900973;
     private static final String APPOP_NONUI_PKG = "appop_nonui_pkg";
     private static final String APPOP_NONUI_CLASS = "appop_nonui_cls";
     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;
@@ -180,17 +196,24 @@
     private EmergencyCallHelper mEmergencyCallHelper;
     private SystemStateHelper.SystemStateListener mSystemStateListener;
     private CarModeTracker mCarModeTracker = spy(new CarModeTracker());
+    private BroadcastReceiver mRegisteredReceiver;
 
     private final int serviceBindingFlags = Context.BIND_AUTO_CREATE
         | Context.BIND_FOREGROUND_SERVICE | Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS
         | Context.BIND_SCHEDULE_LIKE_TOP_APP;
 
+    private UserHandle mChildUserHandle = UserHandle.of(10);
+    private @Mock Call mMockChildUserCall;
+    private UserHandle mParentUserHandle = UserHandle.of(1);
+
     @Override
     @Before
     public void setUp() throws Exception {
         super.setUp();
         MockitoAnnotations.initMocks(this);
         when(mMockCall.getAnalytics()).thenReturn(new Analytics.CallInfo());
+        when(mMockCall.getAssociatedUser()).thenReturn(mUserHandle);
+        when(mMockCall.getId()).thenReturn("TC@1");
         doReturn(mMockResources).when(mMockContext).getResources();
         doReturn(mMockAppOpsManager).when(mMockContext).getSystemService(AppOpsManager.class);
         doReturn(SYS_PKG).when(mMockResources).getString(
@@ -214,6 +237,12 @@
         mInCallController = new InCallController(mMockContext, mLock, mMockCallsManager,
                 mMockSystemStateHelper, mDefaultDialerCache, mTimeoutsAdapter,
                 mEmergencyCallHelper, mCarModeTracker, mClockProxy);
+        // Capture the broadcast receiver registered.
+        doAnswer(invocation -> {
+            mRegisteredReceiver = invocation.getArgument(0);
+            return null;
+        }).when(mMockContext).registerReceiverAsUser(any(BroadcastReceiver.class),
+                any(), any(IntentFilter.class), any(), any());
 
         ArgumentCaptor<SystemStateHelper.SystemStateListener> systemStateListenerArgumentCaptor
                 = ArgumentCaptor.forClass(SystemStateHelper.SystemStateListener.class);
@@ -273,6 +302,13 @@
                 .thenReturn(PackageManager.PERMISSION_DENIED);
 
         when(mMockCallsManager.getAudioState()).thenReturn(new CallAudioState(false, 0, 0));
+
+        when(mMockContext.getSystemService(eq(Context.USER_SERVICE))).thenReturn(mMockUserManager);
+        when(mMockContext.getSystemService(eq(UserManager.class)))
+                .thenReturn(mMockUserManager);
+        // Mock user info to allow binding on user stored in the phone account (mUserHandle).
+        when(mMockUserManager.getUserInfo(anyInt())).thenReturn(mMockUserInfo);
+        when(mMockUserInfo.isManagedProfile()).thenReturn(true);
     }
 
     @Override
@@ -285,6 +321,15 @@
 
     @SmallTest
     @Test
+    public void testBringToForeground_NoInCallServices() {
+        // verify that there is not any bound InCallServices for the user requesting for foreground
+        assertFalse(mInCallController.getInCallServices().containsKey(mUserHandle));
+        // ensure that the method behaves properly on invocation
+        mInCallController.bringToForeground(true /* showDialPad */, mUserHandle /* callingUser */);
+    }
+
+    @SmallTest
+    @Test
     public void testCarModeAppRemoval() {
         setupMockPackageManager(true /* default */, true /* system */, true /* external calls */);
         when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
@@ -348,6 +393,7 @@
         when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
         when(mMockCall.isIncoming()).thenReturn(true);
         when(mMockCall.isExternalCall()).thenReturn(false);
+        when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
         when(mTimeoutsAdapter.getEmergencyCallbackWindowMillis(any(ContentResolver.class)))
                 .thenReturn(300_000L);
 
@@ -359,7 +405,7 @@
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
 
         Intent bindIntent = bindIntentCaptor.getValue();
         assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
@@ -378,6 +424,7 @@
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
         when(mMockCall.isIncoming()).thenReturn(false);
+        when(mMockCall.getAssociatedUser()).thenReturn(mUserHandle);
         when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
         when(mMockCall.getIntentExtras()).thenReturn(callExtras);
         when(mMockCall.isExternalCall()).thenReturn(false);
@@ -393,7 +440,7 @@
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
 
         Intent bindIntent = bindIntentCaptor.getValue();
         assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
@@ -415,13 +462,14 @@
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
         when(mMockCall.isIncoming()).thenReturn(false);
+        when(mMockCall.getAssociatedUser()).thenReturn(mUserHandle);
         when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
         when(mMockCall.getIntentExtras()).thenReturn(callExtras);
         when(mMockCall.isExternalCall()).thenReturn(false);
         when(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID))
                 .thenReturn(DEF_PKG);
         when(mMockContext.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class),
-                anyInt(), eq(UserHandle.CURRENT))).thenReturn(true);
+                anyInt(), eq(mUserHandle))).thenReturn(true);
 
         setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
         mInCallController.bindToServices(mMockCall);
@@ -445,7 +493,7 @@
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
 
         Intent bindIntent = bindIntentCaptor.getValue();
         assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
@@ -467,7 +515,11 @@
         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.getAssociatedUser()).thenReturn(mUserHandle);
         when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
         when(mMockCall.getIntentExtras()).thenReturn(callExtras);
         when(mMockCall.isExternalCall()).thenReturn(false);
@@ -475,7 +527,7 @@
                 .thenReturn(DEF_PKG);
         when(mMockContext.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT))).thenReturn(true);
+                eq(mUserHandle))).thenReturn(true);
         when(mTimeoutsAdapter.getEmergencyCallbackWindowMillis(any(ContentResolver.class)))
                 .thenReturn(300_000L);
 
@@ -503,7 +555,7 @@
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
 
         Intent bindIntent = bindIntentCaptor.getValue();
         assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
@@ -526,6 +578,90 @@
                 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.isIncoming()).thenReturn(true);
+        when(mMockCall.getAssociatedUser()).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_UserAssociatedWithCallIsInQuietMode_NonEmergCallECBM_BindsToPrimaryUser()
+            throws Exception {
+        when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
+        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+        when(mMockCall.isEmergencyCall()).thenReturn(false);
+        when(mMockCall.isInECBM()).thenReturn(true);
+        when(mMockCall.isIncoming()).thenReturn(true);
+        when(mMockCall.getAssociatedUser()).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.getAssociatedUser()).thenReturn(DUMMY_USER_HANDLE);
+        when(mMockContext.getSystemService(eq(UserManager.class)))
+            .thenReturn(mMockUserManager);
+        when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(false);
+        setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
+        setupMockPackageManagerLocationPermission(SYS_PKG, false /* granted */);
+
+        mInCallController.bindToServices(mMockCall);
+
+        ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mMockContext, times(1)).bindServiceAsUser(
+            bindIntentCaptor.capture(),
+            any(ServiceConnection.class),
+            eq(serviceBindingFlags),
+            eq(DUMMY_USER_HANDLE));
+        Intent bindIntent = bindIntentCaptor.getValue();
+        assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
+    }
+
     /**
      * This test verifies the behavior of Telecom when the system dialer crashes on binding and must
      * be restarted.  Specifically, it ensures when the system dialer crashes we revoke the runtime
@@ -542,7 +678,11 @@
         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.getAssociatedUser()).thenReturn(mUserHandle);
         when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
         when(mMockCall.getIntentExtras()).thenReturn(callExtras);
         when(mMockCall.isExternalCall()).thenReturn(false);
@@ -552,7 +692,7 @@
                 ArgumentCaptor.forClass(ServiceConnection.class);
         when(mMockContext.bindServiceAsUser(any(Intent.class), serviceConnectionCaptor.capture(),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT))).thenReturn(true);
+                eq(mUserHandle))).thenReturn(true);
         when(mTimeoutsAdapter.getEmergencyCallbackWindowMillis(any(ContentResolver.class)))
                 .thenReturn(300_000L);
 
@@ -580,7 +720,7 @@
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
 
         Intent bindIntent = bindIntentCaptor.getValue();
         assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
@@ -609,7 +749,7 @@
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
 
         // Verify we were re-granted the runtime permission.
         verify(mMockPackageManager, times(2)).grantRuntimePermission(eq(SYS_PKG),
@@ -629,6 +769,7 @@
         when(mMockCallsManager.getAudioState()).thenReturn(null);
         when(mMockCallsManager.canAddCall()).thenReturn(false);
         when(mMockCall.isIncoming()).thenReturn(false);
+        when(mMockCall.getAssociatedUser()).thenReturn(mUserHandle);
         when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
         when(mMockCall.getIntentExtras()).thenReturn(callExtras);
         when(mMockCall.isExternalCall()).thenReturn(false);
@@ -663,7 +804,7 @@
                 bindIntentCaptor.capture(),
                 serviceConnectionCaptor.capture(),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
 
         Intent bindIntent = bindIntentCaptor.getValue();
         assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
@@ -695,7 +836,7 @@
                 bindIntentCaptor2.capture(),
                 any(ServiceConnection.class),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
 
         bindIntent = bindIntentCaptor2.getValue();
         assertEquals(SYS_PKG, bindIntent.getComponent().getPackageName());
@@ -709,7 +850,9 @@
         when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockCall.isIncoming()).thenReturn(false);
+        when(mMockCall.getAssociatedUser()).thenReturn(mUserHandle);
         when(mMockCall.isExternalCall()).thenReturn(false);
+        when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
         when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
         when(mMockCall.getAnalytics()).thenReturn(mCallInfo);
         when(mMockContext.bindServiceAsUser(
@@ -741,8 +884,9 @@
         // verify(mockInCallService).setInCallAdapter(any(IInCallAdapter.class));
         serviceConnection.onNullBinding(defDialerComponentName);
 
-        verify(mNotificationManager).notify(eq(NOTIFICATION_TAG),
-                eq(IN_CALL_SERVICE_NOTIFICATION_ID), any(Notification.class));
+        verify(mNotificationManager).notifyAsUser(eq(NOTIFICATION_TAG),
+                eq(IN_CALL_SERVICE_NOTIFICATION_ID), any(Notification.class),
+                eq(mUserHandle));
         verify(mCallInfo).addInCallService(eq(defDialerComponentName.flattenToShortString()),
                 anyInt(), anyLong(), eq(true));
 
@@ -751,10 +895,261 @@
                 bindIntentCaptor2.capture(),
                 any(ServiceConnection.class),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
         assertEquals(sysDialerComponentName, bindIntentCaptor2.getValue().getComponent());
     }
 
+    @Test
+    public void testBindToService_CarModeUI_Crash() throws Exception {
+        setupMocks(false /* isExternalCall */);
+        setupMockPackageManager(true /* default */, true /* system */, true /* external calls */);
+
+        // Enable car mode
+        when(mMockSystemStateHelper.isCarModeOrProjectionActive()).thenReturn(true);
+        mInCallController.handleCarModeChange(UiModeManager.DEFAULT_PRIORITY, CAR_PKG, true);
+
+        // Now bind; we should only bind to one app.
+        mInCallController.bindToServices(mMockCall);
+
+        // Bind InCallServices
+        ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        ArgumentCaptor<ServiceConnection> serviceConnectionCaptor =
+                ArgumentCaptor.forClass(ServiceConnection.class);
+        verify(mMockContext, times(1)).bindServiceAsUser(
+                bindIntentCaptor.capture(),
+                serviceConnectionCaptor.capture(),
+                eq(serviceBindingFlags),
+                eq(mUserHandle));
+
+        // Verify bind car mode ui
+        assertEquals(1, bindIntentCaptor.getAllValues().size());
+        verifyBinding(bindIntentCaptor, 0, CAR_PKG, CAR_CLASS);
+
+        // Emulate a crash in the CarModeUI
+        ServiceConnection serviceConnection = serviceConnectionCaptor.getValue();
+        serviceConnection.onServiceDisconnected(bindIntentCaptor.getValue().getComponent());
+
+        ArgumentCaptor<Intent> bindIntentCaptor2 = ArgumentCaptor.forClass(Intent.class);
+        verify(mMockContext, times(2)).bindServiceAsUser(
+                bindIntentCaptor2.capture(),
+                any(ServiceConnection.class),
+                eq(serviceBindingFlags),
+                eq(mUserHandle));
+
+        verifyBinding(bindIntentCaptor2, 1, CAR_PKG, CAR_CLASS);
+    }
+
+    /**
+     * 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
+     * location permission, and when it restarts we re-grant the permission.
+     * @throws Exception
+     */
+    @MediumTest
+    @Test
+    public void testBindToLateConnectionNonUiIcs() throws Exception {
+        Bundle callExtras = new Bundle();
+        callExtras.putBoolean("whatever", true);
+
+        // Make a basic call and bind to the default dialer.
+        when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
+        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);
+        when(mMockCall.isExternalCall()).thenReturn(false);
+        when(mMockCall.getAssociatedUser()).thenReturn(mUserHandle);
+        when(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID))
+                .thenReturn(DEF_PKG);
+        ArgumentCaptor<ServiceConnection> serviceConnectionCaptor =
+                ArgumentCaptor.forClass(ServiceConnection.class);
+        when(mMockContext.bindServiceAsUser(any(Intent.class), serviceConnectionCaptor.capture(),
+                eq(serviceBindingFlags),
+                eq(mUserHandle))).thenReturn(true);
+        when(mTimeoutsAdapter.getEmergencyCallbackWindowMillis(any(ContentResolver.class)))
+                .thenReturn(300_000L);
+
+        // Setup package manager; there is a dialer and disable non-ui ICS
+        when(mMockPackageManager.queryIntentServicesAsUser(
+                any(Intent.class), anyInt(), anyInt())).thenReturn(
+                Arrays.asList(
+                        getDefResolveInfo(false /* externalCalls */, false /* selfMgd */),
+                        getNonUiResolveinfo(false /* selfManaged */,
+                                false /* isEnabled */)
+                )
+        );
+        when(mMockPackageManager
+                .getComponentEnabledSetting(new ComponentName(DEF_PKG, DEF_CLASS)))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+        when(mMockPackageManager
+                .getComponentEnabledSetting(new ComponentName(NONUI_PKG, NONUI_CLASS)))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
+
+        mInCallController.addCall(mMockCall);
+        mInCallController.bindToServices(mMockCall);
+
+        // There will be 4 calls for the various types of ICS.
+        verify(mMockPackageManager, times(4)).queryIntentServicesAsUser(
+                any(Intent.class),
+                eq(PackageManager.GET_META_DATA | PackageManager.MATCH_DISABLED_COMPONENTS),
+                eq(CURRENT_USER_ID));
+
+        // Verify bind to the dialer
+        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());
+        assertEquals(SYS_PKG, bindIntent.getComponent().getPackageName());
+        assertEquals(SYS_CLASS, bindIntent.getComponent().getClassName());
+
+        // Setup mocks to enable nonui ICS
+        when(mMockPackageManager.queryIntentServicesAsUser(
+                any(Intent.class), anyInt(), anyInt())).thenReturn(
+                        Arrays.asList(
+                                getDefResolveInfo(false /* externalCalls */, false /* selfMgd */),
+                                getNonUiResolveinfo(false /* selfManaged */,
+                                        true /* isEnabled */)
+                        )
+        );
+        when(mMockPackageManager
+                .getComponentEnabledSetting(new ComponentName(NONUI_PKG, NONUI_CLASS)))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+
+        // Emulate a late enable of the non-ui ICS
+        Intent packageUpdated = new Intent(Intent.ACTION_PACKAGE_CHANGED);
+        packageUpdated.setData(Uri.fromParts("package", NONUI_PKG, null));
+        packageUpdated.putExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST,
+                new String[] {NONUI_CLASS});
+        packageUpdated.putExtra(Intent.EXTRA_UID, NONUI_UID);
+        mRegisteredReceiver.onReceive(mMockContext, packageUpdated);
+
+        // Now, we expect to auto-rebind to the system dialer (verify 2 times since this is the
+        // second binding).
+        verify(mMockContext, times(2)).bindServiceAsUser(
+                bindIntentCaptor.capture(),
+                any(ServiceConnection.class),
+                eq(serviceBindingFlags),
+                eq(mUserHandle));
+
+        // Unbind!
+        mInCallController.unbindFromServices(UserHandle.of(CURRENT_USER_ID));
+
+        // Make sure we unbound 2 times
+        verify(mMockContext, times(2)).unbindService(any(ServiceConnection.class));
+    }
+
+    /**
+     * Tests a case where InCallController DOES NOT bind to ANY InCallServices when the call is
+     * first added, but then one becomes available after the call starts.  This test was originally
+     * added to reproduce a bug which would cause the call id mapper in the InCallController to not
+     * track a newly added call unless something was bound when the call was first added.
+     * @throws Exception
+     */
+    @MediumTest
+    @Test
+    public void testNoInitialBinding() throws Exception {
+        Bundle callExtras = new Bundle();
+        callExtras.putBoolean("whatever", true);
+
+        // Make a basic call
+        when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
+        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.getAssociatedUser()).thenReturn(mUserHandle);
+        when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
+        when(mMockCall.getIntentExtras()).thenReturn(callExtras);
+        when(mMockCall.isExternalCall()).thenReturn(false);
+        when(mMockCall.isSelfManaged()).thenReturn(true);
+        when(mMockCall.visibleToInCallService()).thenReturn(true);
+
+        // Dialer doesn't handle these calls, but non-UI ICS does.
+        when(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID))
+                .thenReturn(DEF_PKG);
+        ArgumentCaptor<ServiceConnection> serviceConnectionCaptor =
+                ArgumentCaptor.forClass(ServiceConnection.class);
+        when(mMockContext.bindServiceAsUser(any(Intent.class), serviceConnectionCaptor.capture(),
+                eq(serviceBindingFlags),
+                eq(mUserHandle))).thenReturn(true);
+        when(mTimeoutsAdapter.getEmergencyCallbackWindowMillis(any(ContentResolver.class)))
+                .thenReturn(300_000L);
+
+        // Setup package manager; there is a dialer and disable non-ui ICS
+        when(mMockPackageManager.queryIntentServicesAsUser(
+                any(Intent.class), anyInt(), anyInt())).thenReturn(
+                Arrays.asList(
+                        getDefResolveInfo(false /* externalCalls */, false /* selfMgd */),
+                        getNonUiResolveinfo(true /* selfManaged */,
+                                false /* isEnabled */)
+                )
+        );
+        when(mMockPackageManager
+                .getComponentEnabledSetting(new ComponentName(DEF_PKG, DEF_CLASS)))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+        when(mMockPackageManager
+                .getComponentEnabledSetting(new ComponentName(NONUI_PKG, NONUI_CLASS)))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED);
+
+        // Add the call.
+        mInCallController.onCallAdded(mMockCall);
+
+        // There will be 4 calls for the various types of ICS; this is normal.
+        verify(mMockPackageManager, times(4)).queryIntentServicesAsUser(
+                any(Intent.class),
+                eq(PackageManager.GET_META_DATA | PackageManager.MATCH_DISABLED_COMPONENTS),
+                eq(CURRENT_USER_ID));
+
+        // Verify no bind at this point
+        ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mMockContext, never()).bindServiceAsUser(
+                bindIntentCaptor.capture(),
+                any(ServiceConnection.class),
+                eq(serviceBindingFlags),
+                eq(mUserHandle));
+
+        // Setup mocks to enable non-ui ICS
+        when(mMockPackageManager.queryIntentServicesAsUser(
+                any(Intent.class), anyInt(), anyInt())).thenReturn(
+                Arrays.asList(
+                        getDefResolveInfo(false /* externalCalls */, false /* selfMgd */),
+                        getNonUiResolveinfo(true /* selfManaged */,
+                                true /* isEnabled */)
+                )
+        );
+        when(mMockPackageManager
+                .getComponentEnabledSetting(new ComponentName(NONUI_PKG, NONUI_CLASS)))
+                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
+
+        // Emulate a late enable of the non-ui ICS
+        Intent packageUpdated = new Intent(Intent.ACTION_PACKAGE_CHANGED);
+        packageUpdated.setData(Uri.fromParts("package", NONUI_PKG, null));
+        packageUpdated.putExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST,
+                new String[] {NONUI_CLASS});
+        packageUpdated.putExtra(Intent.EXTRA_UID, NONUI_UID);
+        mRegisteredReceiver.onReceive(mMockContext, packageUpdated);
+
+        // Make sure we bound to it.
+        verify(mMockContext, times(1)).bindServiceAsUser(
+                bindIntentCaptor.capture(),
+                any(ServiceConnection.class),
+                eq(serviceBindingFlags),
+                eq(mUserHandle));
+    }
+
     /**
      * Ensures that the {@link InCallController} will bind to an {@link InCallService} which
      * supports external calls.
@@ -785,7 +1180,7 @@
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
 
         Intent bindIntent = bindIntentCaptor.getValue();
         assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
@@ -805,6 +1200,7 @@
         when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
         when(mMockCall.isIncoming()).thenReturn(true);
         when(mMockCall.isExternalCall()).thenReturn(false);
+        when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
         when(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID)).thenReturn(DEF_PKG);
         when(mMockContext.bindServiceAsUser(nullable(Intent.class),
                 nullable(ServiceConnection.class), anyInt(), nullable(UserHandle.class)))
@@ -823,7 +1219,7 @@
                 bindIntentCaptor.capture(),
                 serviceConnectionCaptor.capture(),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
 
         // Pretend that the call has gone away.
         when(mMockCallsManager.getCalls()).thenReturn(Collections.emptyList());
@@ -871,7 +1267,7 @@
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
         // Verify bind car mode ui
         assertEquals(1, bindIntentCaptor.getAllValues().size());
         verifyBinding(bindIntentCaptor, 0, CAR_PKG, CAR_CLASS);
@@ -899,7 +1295,7 @@
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
         // Verify bind to default package, instead of the invalid car mode ui.
         assertEquals(1, bindIntentCaptor.getAllValues().size());
         verifyBinding(bindIntentCaptor, 0, DEF_PKG, DEF_CLASS);
@@ -942,7 +1338,7 @@
                     bindIntentCaptor.capture(),
                     any(ServiceConnection.class),
                     eq(serviceBindingFlags),
-                    eq(UserHandle.CURRENT));
+                    eq(mUserHandle));
 
             // Verify bind
             assertEquals(2, bindIntentCaptor.getAllValues().size());
@@ -986,7 +1382,7 @@
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
 
         // Verify bind
         assertEquals(1, bindIntentCaptor.getAllValues().size());
@@ -995,8 +1391,10 @@
         verifyBinding(bindIntentCaptor, 0, NONUI_PKG, NONUI_CLASS);
 
         // Verify notification is not sent by NotificationManager
-        verify(mNotificationManager, times(0)).notify(eq(InCallController.NOTIFICATION_TAG),
-                eq(InCallController.IN_CALL_SERVICE_NOTIFICATION_ID), any());
+        verify(mNotificationManager, times(0)).notifyAsUser(
+                eq(InCallController.NOTIFICATION_TAG),
+                eq(InCallController.IN_CALL_SERVICE_NOTIFICATION_ID), any(),
+                eq(mUserHandle));
     }
 
     @MediumTest
@@ -1019,7 +1417,7 @@
                 bindIntentCaptor.capture(),
                 serviceConnectionCaptor.capture(),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
         assertEquals(1, bindIntentCaptor.getAllValues().size());
         verifyBinding(bindIntentCaptor, 0, DEF_PKG, DEF_CLASS);
 
@@ -1057,7 +1455,7 @@
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
     }
 
     /**
@@ -1088,7 +1486,7 @@
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
         // Verify bind car mode ui
         assertEquals(4, bindIntentCaptor.getAllValues().size());
 
@@ -1124,7 +1522,9 @@
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
         when(mMockCall.isIncoming()).thenReturn(false);
+        when(mMockCall.getAssociatedUser()).thenReturn(mUserHandle);
         when(mMockCall.isExternalCall()).thenReturn(false);
+        when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
         when(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID)).thenReturn(DEF_PKG);
         when(mMockContext.bindServiceAsUser(nullable(Intent.class),
                 nullable(ServiceConnection.class), anyInt(), nullable(UserHandle.class)))
@@ -1145,7 +1545,7 @@
                 bindIntentCaptor.capture(),
                 serviceConnectionCaptor.capture(),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
 
         CompletableFuture<Boolean> bindTimeout = mInCallController.getBindingFuture();
 
@@ -1213,7 +1613,7 @@
                 any(Intent.class),
                 any(ServiceConnection.class),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
 
         // Now switch to car mode.
         // Enable car mode and enter car mode at default priority.
@@ -1225,7 +1625,7 @@
                 bindIntentCaptor.capture(),
                 any(ServiceConnection.class),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
         // Verify bind car mode ui
         assertEquals(1, bindIntentCaptor.getAllValues().size());
         verifyBinding(bindIntentCaptor, 0, CAR_PKG, CAR_CLASS);
@@ -1251,7 +1651,7 @@
                 any(Intent.class),
                 any(ServiceConnection.class),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
 
         // Now switch to car mode.
         // Enable car mode and enter car mode at default priority.
@@ -1268,7 +1668,7 @@
                 any(Intent.class),
                 serviceConnectionCaptor.capture(),
                 eq(serviceBindingFlags),
-                eq(UserHandle.CURRENT));
+                eq(mUserHandle));
 
         ServiceConnection serviceConnection = serviceConnectionCaptor.getValue();
         ComponentName defDialerComponentName = new ComponentName(DEF_PKG, DEF_CLASS);
@@ -1284,6 +1684,191 @@
         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));
+    }
+
+    @Test
+    public void testSecondaryUserCallBindToCurrentUser() throws Exception {
+        setupMocks(true /* isExternalCall */);
+        setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
+        // Force the difference between the phone account user and current user. This is supposed to
+        // simulate a secondary user placing a call over an unassociated sim.
+        assertFalse(mUserHandle.equals(UserHandle.USER_CURRENT));
+        when(mMockUserInfo.isManagedProfile()).thenReturn(false);
+
+        mInCallController.bindToServices(mMockCall);
+
+        // Bind InCallService on UserHandle.CURRENT and not the user from the call (mUserHandle)
+        ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mMockContext, times(1)).bindServiceAsUser(
+                bindIntentCaptor.capture(),
+                any(ServiceConnection.class),
+                eq(serviceBindingFlags),
+                eq(UserHandle.CURRENT));
+    }
+
+    @Test
+    public void testGetUserFromCall_TargetPhoneAccountNotSet() throws Exception {
+        setupMocks(false /* isExternalCall */);
+        setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
+        UserHandle testUser = new UserHandle(10);
+
+        when(mMockCall.getTargetPhoneAccount()).thenReturn(null);
+        when(mMockCall.getAssociatedUser()).thenReturn(testUser);
+
+        // Bind to ICS. The mapping should've been inserted with the testUser as the key.
+        mInCallController.bindToServices(mMockCall);
+        assertTrue(mInCallController.getInCallServiceConnections().containsKey(testUser));
+
+        // Set the target phone account. Simulates the flow when the user has chosen which sim to
+        // place the call on.
+        when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
+
+        // Remove the call. This invokes getUserFromCall to remove the ICS mapping.
+        when(mMockCallsManager.getCalls()).thenReturn(Collections.emptyList());
+        mInCallController.onCallRemoved(mMockCall);
+        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+
+        // Verify that the mapping was properly removed.
+        assertNull(mInCallController.getInCallServiceConnections().get(testUser));
+    }
+
+    @Test
+    public void testGetUserFromCall_IncomingCall() throws Exception {
+        setupMocks(false /* isExternalCall */);
+        setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
+        // Explicitly test on a different user to avoid interference with current user.
+        UserHandle testUser = new UserHandle(10);
+
+        // Set user handle in target phone account to test user
+        when(mMockCall.getAssociatedUser()).thenReturn(testUser);
+        when(mMockCall.isIncoming()).thenReturn(true);
+
+        // Bind to ICS. The mapping should've been inserted with the testUser as the key.
+        mInCallController.bindToServices(mMockCall);
+        assertTrue(mInCallController.getInCallServiceConnections().containsKey(testUser));
+
+        // Remove the call. This invokes getUserFromCall to remove the ICS mapping.
+        when(mMockCallsManager.getCalls()).thenReturn(Collections.emptyList());
+        mInCallController.onCallRemoved(mMockCall);
+        waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
+
+        // Verify that the mapping was properly removed.
+        assertNull(mInCallController.getInCallServiceConnections().get(testUser));
+    }
+
+    private void setupMocksForWorkProfileTest() {
+        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+        when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
+        when(mMockChildUserCall.isIncoming()).thenReturn(false);
+        when(mMockChildUserCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
+        when(mDefaultDialerCache.getDefaultDialerApplication(CURRENT_USER_ID)).thenReturn(DEF_PKG);
+        when(mMockContext.bindServiceAsUser(any(Intent.class), any(ServiceConnection.class),
+                anyInt(), any())).thenReturn(true);
+        when(mMockChildUserCall.isExternalCall()).thenReturn(false);
+        when(mMockChildUserCall.isSelfManaged()).thenReturn(true);
+        when(mMockChildUserCall.visibleToInCallService()).thenReturn(true);
+
+        //Setup up parent and child/work profile relation
+        when(mMockUserInfo.getUserHandle()).thenReturn(mParentUserHandle);
+        when(mMockChildUserInfo.getUserHandle()).thenReturn(mChildUserHandle);
+        when(mMockUserInfo.isManagedProfile()).thenReturn(false);
+        when(mMockChildUserInfo.isManagedProfile()).thenReturn(true);
+        when(mMockChildUserCall.getAssociatedUser()).thenReturn(mChildUserHandle);
+        when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mChildUserHandle);
+        when(mMockUserManager.getProfileParent(mChildUserHandle.getIdentifier())).thenReturn(
+                mMockUserInfo);
+        when(mMockUserManager.getProfileParent(mChildUserHandle)).thenReturn(mParentUserHandle);
+        when(mMockUserManager.getUserInfo(eq(mParentUserHandle.getIdentifier()))).thenReturn(
+                mMockUserInfo);
+        when(mMockUserManager.getUserInfo(eq(mChildUserHandle.getIdentifier()))).thenReturn(
+                mMockChildUserInfo);
+        when(mMockUserManager.isManagedProfile(mChildUserHandle.getIdentifier())).thenReturn(true);
+        when(mMockUserManager.isManagedProfile(mParentUserHandle.getIdentifier())).thenReturn(
+                false);
+    }
+
+    @Test
+    public void testManagedProfileCallQueriesIcsUsingParentUserToo() throws Exception {
+        setupMocksForWorkProfileTest();
+        setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
+        setupMockPackageManager(true /* default */,
+                true /*useNonUiInCalls*/, true /*useAppOpNonUiInCalls*/,
+                true /*useSystemDialer*/, false /*includeExternalCalls*/,
+                true /*includeSelfManagedCallsInDefaultDialer*/,
+                true /*includeSelfManagedCallsInCarModeDialer*/,
+                true /*includeSelfManagedCallsInNonUi*/);
+
+        //pass in call by child/work-profileuser
+        mInCallController.bindToServices(mMockChildUserCall);
+
+        // Verify that queryIntentServicesAsUser is also called with parent handle
+        // Query for the different InCallServices
+        ArgumentCaptor<Integer> userIdCaptor = ArgumentCaptor.forClass(Integer.class);
+        ArgumentCaptor<Intent> queryIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        ArgumentCaptor<Integer> flagCaptor = ArgumentCaptor.forClass(Integer.class);
+        verify(mMockPackageManager, times(6)).queryIntentServicesAsUser(
+                queryIntentCaptor.capture(), flagCaptor.capture(), userIdCaptor.capture());
+        List<Integer> userIds = userIdCaptor.getAllValues();
+
+        //check if queryIntentServices was called with child user handle
+        assertTrue("no query parent user handle",
+                userIds.contains(mChildUserHandle.getIdentifier()));
+        //check if queryIntentServices was also called with parent user handle
+        assertTrue("no query parent user handle",
+                userIds.contains(mParentUserHandle.getIdentifier()));
+    }
+
     private void setupMocks(boolean isExternalCall) {
         setupMocks(isExternalCall, false /* isSelfManagedCall */);
     }
@@ -1293,10 +1878,11 @@
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
         when(mMockCall.isIncoming()).thenReturn(false);
+        when(mMockCall.getAssociatedUser()).thenReturn(mUserHandle);
         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);
@@ -1378,14 +1964,14 @@
         }};
     }
 
-    private ResolveInfo getNonUiResolveinfo(boolean supportsSelfManaged) {
+    private ResolveInfo getNonUiResolveinfo(boolean supportsSelfManaged, boolean isEnabled) {
         return new ResolveInfo() {{
             serviceInfo = new ServiceInfo();
             serviceInfo.packageName = NONUI_PKG;
             serviceInfo.name = NONUI_CLASS;
             serviceInfo.applicationInfo = new ApplicationInfo();
             serviceInfo.applicationInfo.uid = NONUI_UID;
-            serviceInfo.enabled = true;
+            serviceInfo.enabled = isEnabled;
             serviceInfo.permission = Manifest.permission.BIND_INCALL_SERVICE;
             serviceInfo.metaData = new Bundle();
             if (supportsSelfManaged) {
@@ -1476,7 +2062,7 @@
                 } else {
                     // InCallController uses a blank package name when querying for non-ui incalls
                     if (useNonUiInCalls) {
-                        resolveInfo.add(getNonUiResolveinfo(includeSelfManagedCallsInNonUi));
+                        resolveInfo.add(getNonUiResolveinfo(includeSelfManagedCallsInNonUi, true));
                     }
                     // InCallController uses a blank package name when querying for App Op non-ui incalls
                     if (useAppOpNonUiInCalls) {
@@ -1487,7 +2073,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..914fdc5 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.getAssociatedUser()).
+                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.getAssociatedUser()).
+                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..4af3de3 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.getAssociatedUser()).
+                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/MmiUtilsTest.java b/tests/src/com/android/server/telecom/tests/MmiUtilsTest.java
new file mode 100644
index 0000000..ed74637
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/MmiUtilsTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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 org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.net.Uri;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.telecom.MmiUtils;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class MmiUtilsTest extends TelecomTestCase {
+
+    private static final String[] sDangerousDialStrings = {
+        "*21*1234567#", // fwd unconditionally to 1234567,
+        "*67*1234567#", // fwd to 1234567 when line is busy
+        "*61*1234567#", // fwd to 1234567 when no one picks up
+        "*62*1234567#", // fwd to 1234567 when out of range
+        "*004*1234567#", // fwd to 1234567 when busy, not pickup up, out of range
+        "*004*1234567#", // fwd to 1234567 conditionally
+        "**21*1234567#", // fwd unconditionally to 1234567
+
+        // north american vertical service codes
+
+        "*094565678", // Selective Call Blocking/Reporting
+        "*4278889", // Change Forward-To Number for Customer Programmable Call Forwarding Don't
+                    // Answer
+        "*5644456", // Change Forward-To Number for ISDN Call Forwarding
+        "*6045677", // Selective Call Rejection Activation
+        "*635678", // Selective Call Forwarding Activation
+        "*64678899", // Selective Call Acceptance Activation
+        "*683456", // Call Forwarding Busy Line/Don't Answer Activation
+        "*721234", // Call Forwarding Activation
+        "*77", // Anonymous Call Rejection Activation
+        "*78", // Do Not Disturb Activation
+    };
+
+    private MmiUtils mMmiUtils = new MmiUtils();
+    private static final String[] sNonDangerousDialStrings = {"*6712345678", "*272", "*272911"};
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @Override
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    @SmallTest
+    @Test
+    public void testDangerousDialStringsDetected() throws Exception {
+        for (String s : sDangerousDialStrings) {
+            Uri.Builder b = new Uri.Builder();
+            b.scheme("tel").opaquePart(s);
+            assertTrue(mMmiUtils.isDangerousMmiOrVerticalCode(b.build()));
+        }
+    }
+
+    @SmallTest
+    @Test
+    public void testNonDangerousDialStringsNotDetected() throws Exception {
+        for (String s : sNonDangerousDialStrings) {
+            Uri.Builder b = new Uri.Builder();
+            b.scheme("tel").opaquePart(s);
+            assertFalse(mMmiUtils.isDangerousMmiOrVerticalCode(b.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..33acd98 100644
--- a/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
+++ b/tests/src/com/android/server/telecom/tests/NewOutgoingCallIntentBroadcasterTest.java
@@ -56,6 +56,7 @@
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.DefaultDialerCache;
+import com.android.server.telecom.MmiUtils;
 import com.android.server.telecom.NewOutgoingCallIntentBroadcaster;
 import com.android.server.telecom.PhoneAccountRegistrar;
 import com.android.server.telecom.PhoneNumberUtilsAdapter;
@@ -93,13 +94,14 @@
     @Mock private RoleManagerAdapter mRoleManagerAdapter;
     @Mock private DefaultDialerCache mDefaultDialerCache;
 
+    @Mock private MmiUtils mMmiUtils;
     private PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter = new PhoneNumberUtilsAdapterImpl();
 
     @Override
     @Before
     public void setUp() throws Exception {
         super.setUp();
-        when(mCall.getInitiatingUser()).thenReturn(UserHandle.CURRENT);
+        when(mCall.getAssociatedUser()).thenReturn(UserHandle.CURRENT);
         when(mCallsManager.getLock()).thenReturn(new TelecomSystem.SyncRoot() { });
         when(mCallsManager.getSystemStateHelper()).thenReturn(mSystemStateHelper);
         when(mCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
@@ -232,7 +234,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";
@@ -261,6 +263,58 @@
         assertEquals(Intent.FLAG_ACTIVITY_NEW_TASK, dialerIntent.getFlags());
     }
 
+    @Test
+    public void testDangerousMmiCodeWithNonDefaultDialer() {
+        Uri handle = Uri.parse("tel:*21*1234567#");
+        doReturn(true).when(mMmiUtils).isDangerousMmiOrVerticalCode(handle);
+        Intent intent = new Intent(Intent.ACTION_CALL, handle);
+
+        String ui_package_string = "sample_string_1";
+        String dialer_default_class_string = "sample_string_2";
+        mComponentContextFixture.putResource(com.android.internal.R.string.config_defaultDialer,
+                ui_package_string);
+        mComponentContextFixture.putResource(R.string.dialer_default_class,
+                dialer_default_class_string);
+        when(mDefaultDialerCache.getSystemDialerApplication()).thenReturn(ui_package_string);
+        when(mDefaultDialerCache.getDialtactsSystemDialerComponent()).thenReturn(
+                new ComponentName(ui_package_string, dialer_default_class_string));
+
+        int result = processIntent(intent, false).disconnectCause;
+
+        assertEquals(DisconnectCause.OUTGOING_CANCELED, result);
+        verifyNoBroadcastSent();
+        verifyNoCallPlaced();
+
+        ArgumentCaptor<Intent> dialerIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext).startActivityAsUser(dialerIntentCaptor.capture(), any(UserHandle.class));
+        Intent dialerIntent = dialerIntentCaptor.getValue();
+        assertEquals(new ComponentName(ui_package_string, dialer_default_class_string),
+                dialerIntent.getComponent());
+        assertEquals(Intent.ACTION_DIAL, dialerIntent.getAction());
+        assertEquals(handle, dialerIntent.getData());
+        assertEquals(Intent.FLAG_ACTIVITY_NEW_TASK, dialerIntent.getFlags());
+    }
+
+    @Test
+    public void testNonDangerousMmiCodeWithNonDefaultDialer() {
+        Uri handle = Uri.parse("tel:*12*1234567#");
+        doReturn(false).when(mMmiUtils).isDangerousMmiOrVerticalCode(handle);
+        Intent intent = new Intent(Intent.ACTION_CALL, handle);
+
+        String ui_package_string = "sample_string_1";
+        String dialer_default_class_string = "sample_string_2";
+        mComponentContextFixture.putResource(com.android.internal.R.string.config_defaultDialer,
+                ui_package_string);
+        mComponentContextFixture.putResource(R.string.dialer_default_class,
+                dialer_default_class_string);
+        when(mDefaultDialerCache.getSystemDialerApplication()).thenReturn(ui_package_string);
+        when(mDefaultDialerCache.getDialtactsSystemDialerComponent()).thenReturn(
+                new ComponentName(ui_package_string, dialer_default_class_string));
+
+        int result = processIntent(intent, false).disconnectCause;
+        assertEquals(DisconnectCause.NOT_DISCONNECTED, result);
+    }
+
     @SmallTest
     @Test
     public void testActionCallEmergencyCall() {
@@ -303,7 +357,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 +371,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 +493,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));
     }
@@ -488,7 +542,7 @@
             boolean isDefaultPhoneApp) {
         NewOutgoingCallIntentBroadcaster b = new NewOutgoingCallIntentBroadcaster(
                 mContext, mCallsManager, intent, mPhoneNumberUtilsAdapter,
-                isDefaultPhoneApp, mDefaultDialerCache);
+                isDefaultPhoneApp, mDefaultDialerCache, mMmiUtils);
         NewOutgoingCallIntentBroadcaster.CallDisposition cd = b.evaluateCall();
         if (cd.disconnectCause == DisconnectCause.NOT_DISCONNECTED) {
             b.processCall(mCall, cd);
diff --git a/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java b/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java
index a503283..fed8084 100644
--- a/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java
+++ b/tests/src/com/android/server/telecom/tests/ParcelableCallUtilsTest.java
@@ -87,7 +87,7 @@
     @SmallTest
     @Test
     public void testParcelForNonSystemDialer() {
-        mCall.putExtras(Call.SOURCE_CONNECTION_SERVICE, getSomeExtras());
+        mCall.putConnectionServiceExtras(getSomeExtras());
         ParcelableCall call = ParcelableCallUtils.toParcelableCall(mCall,
                 false /* includevideoProvider */,
                 null /* phoneAccountRegistrar */,
@@ -105,7 +105,7 @@
     @SmallTest
     @Test
     public void testParcelForSystemDialer() {
-        mCall.putExtras(Call.SOURCE_CONNECTION_SERVICE, getSomeExtras());
+        mCall.putConnectionServiceExtras(getSomeExtras());
         ParcelableCall call = ParcelableCallUtils.toParcelableCall(mCall,
                 false /* includevideoProvider */,
                 null /* phoneAccountRegistrar */,
@@ -123,7 +123,7 @@
     @SmallTest
     @Test
     public void testParcelForSystemCallScreening() {
-        mCall.putExtras(Call.SOURCE_CONNECTION_SERVICE, getSomeExtras());
+        mCall.putConnectionServiceExtras(getSomeExtras());
         ParcelableCall call = ParcelableCallUtils.toParcelableCallForScreening(mCall,
                 true /* isPartOfSystemDialer */);
 
@@ -137,7 +137,7 @@
     @SmallTest
     @Test
     public void testParcelForSystemNonSystemCallScreening() {
-        mCall.putExtras(Call.SOURCE_CONNECTION_SERVICE, getSomeExtras());
+        mCall.putConnectionServiceExtras(getSomeExtras());
         ParcelableCall call = ParcelableCallUtils.toParcelableCallForScreening(mCall,
                 false /* isPartOfSystemDialer */);
 
diff --git a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
index ffa08e2..e573bb8 100644
--- a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
+++ b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
@@ -22,11 +22,15 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Matchers.anyBoolean;
 import static org.mockito.Matchers.anyInt;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -83,22 +87,30 @@
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.UUID;
 
 @RunWith(JUnit4.class)
 public class PhoneAccountRegistrarTest extends TelecomTestCase {
 
     private static final int MAX_VERSION = Integer.MAX_VALUE;
+    private static final int INVALID_CHAR_LIMIT_COUNT =
+            PhoneAccountRegistrar.MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT + 1;
+    private static final String INVALID_STR = "a".repeat(INVALID_CHAR_LIMIT_COUNT);
     private static final String FILE_NAME = "phone-account-registrar-test-1223.xml";
     private static final String TEST_LABEL = "right";
+    private static final String TEST_ID = "123";
     private final String PACKAGE_1 = "PACKAGE_1";
     private final String PACKAGE_2 = "PACKAGE_2";
     private final String COMPONENT_NAME = "com.android.server.telecom.tests.MockConnectionService";
+    private final UserHandle USER_HANDLE_10 = new UserHandle(10);
     private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
     private PhoneAccountRegistrar mRegistrar;
     @Mock private SubscriptionManager mSubscriptionManager;
@@ -163,7 +175,7 @@
         testBundle.putString("EXTRA_STR1", "Hello");
         testBundle.putString("EXTRA_STR2", "There");
 
-        PhoneAccount input = makeQuickAccountBuilder("id0", 0)
+        PhoneAccount input = makeQuickAccountBuilder("id0", 0, null)
                 .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
                 .addSupportedUriScheme(PhoneAccount.SCHEME_VOICEMAIL)
                 .setExtras(testBundle)
@@ -271,7 +283,7 @@
         // Put in something valid so the bundle exists.
         testBundle.putString("EXTRA_OK", "OK");
 
-        PhoneAccount input = makeQuickAccountBuilder("id0", 0)
+        PhoneAccount input = makeQuickAccountBuilder("id0", 0, null)
                 .addSupportedUriScheme(PhoneAccount.SCHEME_TEL)
                 .addSupportedUriScheme(PhoneAccount.SCHEME_VOICEMAIL)
                 .setExtras(testBundle)
@@ -309,24 +321,33 @@
         mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
                 Mockito.mock(IConnectionService.class));
 
-        registerAndEnableAccount(makeQuickAccountBuilder("id" + i, i++)
+        registerAndEnableAccount(makeQuickAccountBuilder("id" + i, i++, null)
                 .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER
                         | PhoneAccount.CAPABILITY_CALL_PROVIDER)
                 .build());
-        registerAndEnableAccount(makeQuickAccountBuilder("id" + i, i++)
+        registerAndEnableAccount(makeQuickAccountBuilder("id" + i, i++, null)
                 .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER
                         | PhoneAccount.CAPABILITY_CALL_PROVIDER)
                 .build());
-        registerAndEnableAccount(makeQuickAccountBuilder("id" + i, i++)
+        registerAndEnableAccount(makeQuickAccountBuilder("id" + i, i++, null)
                 .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER
                         | PhoneAccount.CAPABILITY_CALL_PROVIDER)
                 .build());
-        registerAndEnableAccount(makeQuickAccountBuilder("id" + i, i++)
+        registerAndEnableAccount(makeQuickAccountBuilder("id" + i, i++, null)
+                .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER)
+                .build());
+        registerAndEnableAccount(makeQuickAccountBuilder("id" + i, i++, USER_HANDLE_10)
+                .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER
+                        | PhoneAccount.CAPABILITY_CALL_PROVIDER)
+                .build());
+        registerAndEnableAccount(makeQuickAccountBuilder("id" + i, i++, USER_HANDLE_10)
                 .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER)
                 .build());
 
-        assertEquals(4, mRegistrar.getAllPhoneAccountsOfCurrentUser().size());
-        assertEquals(3, mRegistrar.getCallCapablePhoneAccountsOfCurrentUser(null, false).size());
+        assertEquals(6, mRegistrar.
+                getAllPhoneAccounts(null, true).size());
+        assertEquals(4, mRegistrar.getCallCapablePhoneAccounts(null, false,
+                null, true).size());
         assertEquals(null, mRegistrar.getSimCallManagerOfCurrentUser());
         assertEquals(null, mRegistrar.getOutgoingPhoneAccountForSchemeOfCurrentUser(
                 PhoneAccount.SCHEME_TEL));
@@ -674,6 +695,75 @@
         assertEquals(TEST_LABEL, registeredAccount.getLabel());
     }
 
+    @MediumTest
+    @Test
+    public void testSecurityExceptionIsThrownWhenSelfManagedLacksPermissions() {
+        PhoneAccountHandle handle = makeQuickAccountHandle(
+                new ComponentName("self", "managed"), "selfie1");
+
+        PhoneAccount accountWithoutCapability = new PhoneAccount.Builder(handle, "label")
+                .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
+                .build();
+
+        assertFalse(mRegistrar.hasTransactionalCallCapabilities(accountWithoutCapability));
+
+        try {
+            mRegistrar.registerPhoneAccount(accountWithoutCapability);
+            fail("should not be able to register account");
+        } catch (SecurityException securityException) {
+            // test pass
+        } finally {
+            mRegistrar.unregisterPhoneAccount(handle);
+        }
+    }
+
+    @MediumTest
+    @Test
+    public void testSelfManagedPhoneAccountWithTransactionalOperations() {
+        PhoneAccountHandle handle = makeQuickAccountHandle(
+                new ComponentName("self", "managed"), "selfie1");
+
+        PhoneAccount accountWithCapability = new PhoneAccount.Builder(handle, "label")
+                .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED |
+                        PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS)
+                .build();
+
+        assertTrue(mRegistrar.hasTransactionalCallCapabilities(accountWithCapability));
+
+        try {
+            mRegistrar.registerPhoneAccount(accountWithCapability);
+            PhoneAccount registeredAccount = mRegistrar.getPhoneAccountUnchecked(handle);
+            assertEquals(TEST_LABEL, registeredAccount.getLabel().toString());
+        } finally {
+            mRegistrar.unregisterPhoneAccount(handle);
+        }
+    }
+
+    @MediumTest
+    @Test
+    public void testRegisterPhoneAccountAmendsSelfManagedCapabilityInternally() {
+        // GIVEN
+        PhoneAccountHandle handle = makeQuickAccountHandle(
+                new ComponentName("self", "managed"), "selfie1");
+        PhoneAccount accountWithCapability = new PhoneAccount.Builder(handle, "label")
+                .setCapabilities(
+                        PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS)
+                .build();
+
+        assertTrue(mRegistrar.hasTransactionalCallCapabilities(accountWithCapability));
+
+        try {
+            // WHEN
+            mRegistrar.registerPhoneAccount(accountWithCapability);
+            PhoneAccount registeredAccount = mRegistrar.getPhoneAccountUnchecked(handle);
+            // THEN
+            assertEquals(PhoneAccount.CAPABILITY_SELF_MANAGED, (registeredAccount.getCapabilities()
+                    & PhoneAccount.CAPABILITY_SELF_MANAGED));
+        } finally {
+            mRegistrar.unregisterPhoneAccount(handle);
+        }
+    }
+
     /**
      * Tests to ensure that when registering a self-managed PhoneAccount, it cannot also be defined
      * as a call provider, connection manager, or sim subscription.
@@ -727,7 +817,7 @@
         registerAndEnableAccount(nonSimAccount);
         registerAndEnableAccount(simAccount);
 
-        List<PhoneAccount> accounts = mRegistrar.getAllPhoneAccounts(Process.myUserHandle());
+        List<PhoneAccount> accounts = mRegistrar.getAllPhoneAccounts(Process.myUserHandle(), false);
         assertTrue(accounts.get(0).getLabel().toString().equals("2"));
         assertTrue(accounts.get(1).getLabel().toString().equals("1"));
     }
@@ -770,7 +860,7 @@
         registerAndEnableAccount(account2);
         registerAndEnableAccount(account1);
 
-        List<PhoneAccount> accounts = mRegistrar.getAllPhoneAccounts(Process.myUserHandle());
+        List<PhoneAccount> accounts = mRegistrar.getAllPhoneAccounts(Process.myUserHandle(), false);
         assertTrue(accounts.get(0).getLabel().toString().equals("c"));
         assertTrue(accounts.get(1).getLabel().toString().equals("b"));
         assertTrue(accounts.get(2).getLabel().toString().equals("a"));
@@ -808,7 +898,7 @@
         registerAndEnableAccount(account2);
         registerAndEnableAccount(account3);
 
-        List<PhoneAccount> accounts = mRegistrar.getAllPhoneAccounts(Process.myUserHandle());
+        List<PhoneAccount> accounts = mRegistrar.getAllPhoneAccounts(Process.myUserHandle(), false);
         assertTrue(accounts.get(0).getLabel().toString().equals("a"));
         assertTrue(accounts.get(1).getLabel().toString().equals("b"));
         assertTrue(accounts.get(2).getLabel().toString().equals("c"));
@@ -886,7 +976,7 @@
         registerAndEnableAccount(account5);
         registerAndEnableAccount(account6);
 
-        List<PhoneAccount> accounts = mRegistrar.getAllPhoneAccounts(Process.myUserHandle());
+        List<PhoneAccount> accounts = mRegistrar.getAllPhoneAccounts(Process.myUserHandle(), false);
         // Sim accts ordered by sort order first
         assertTrue(accounts.get(0).getLabel().toString().equals("z"));
         assertTrue(accounts.get(1).getLabel().toString().equals("y"));
@@ -910,14 +1000,14 @@
     public void testGetByEnabledState() throws Exception {
         mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
                 Mockito.mock(IConnectionService.class));
-        mRegistrar.registerPhoneAccount(makeQuickAccountBuilder("id1", 1)
+        mRegistrar.registerPhoneAccount(makeQuickAccountBuilder("id1", 1, null)
                 .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                 .build());
 
         assertEquals(0, mRegistrar.getCallCapablePhoneAccounts(PhoneAccount.SCHEME_TEL,
-                false /* includeDisabled */, Process.myUserHandle()).size());
+                false /* includeDisabled */, Process.myUserHandle(), false).size());
         assertEquals(1, mRegistrar.getCallCapablePhoneAccounts(PhoneAccount.SCHEME_TEL,
-                true /* includeDisabled */, Process.myUserHandle()).size());
+                true /* includeDisabled */, Process.myUserHandle(), false).size());
     }
 
     /**
@@ -930,21 +1020,21 @@
     public void testGetByScheme() throws Exception {
         mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
                 Mockito.mock(IConnectionService.class));
-        registerAndEnableAccount(makeQuickAccountBuilder("id1", 1)
+        registerAndEnableAccount(makeQuickAccountBuilder("id1", 1, null)
                 .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                 .setSupportedUriSchemes(Arrays.asList(PhoneAccount.SCHEME_SIP))
                 .build());
-        registerAndEnableAccount(makeQuickAccountBuilder("id2", 2)
+        registerAndEnableAccount(makeQuickAccountBuilder("id2", 2, null)
                 .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                 .setSupportedUriSchemes(Arrays.asList(PhoneAccount.SCHEME_TEL))
                 .build());
 
         assertEquals(1, mRegistrar.getCallCapablePhoneAccounts(PhoneAccount.SCHEME_SIP,
-                false /* includeDisabled */, Process.myUserHandle()).size());
+                false /* includeDisabled */, Process.myUserHandle(), false).size());
         assertEquals(1, mRegistrar.getCallCapablePhoneAccounts(PhoneAccount.SCHEME_TEL,
-                false /* includeDisabled */, Process.myUserHandle()).size());
+                false /* includeDisabled */, Process.myUserHandle(), false).size());
         assertEquals(2, mRegistrar.getCallCapablePhoneAccounts(null, false /* includeDisabled */,
-                Process.myUserHandle()).size());
+                Process.myUserHandle(), false).size());
     }
 
     /**
@@ -957,23 +1047,24 @@
     public void testGetByCapability() throws Exception {
         mComponentContextFixture.addConnectionService(makeQuickConnectionServiceComponentName(),
                 Mockito.mock(IConnectionService.class));
-        registerAndEnableAccount(makeQuickAccountBuilder("id1", 1)
+        registerAndEnableAccount(makeQuickAccountBuilder("id1", 1, null)
                 .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER
                         | PhoneAccount.CAPABILITY_VIDEO_CALLING)
                 .setSupportedUriSchemes(Arrays.asList(PhoneAccount.SCHEME_SIP))
                 .build());
-        registerAndEnableAccount(makeQuickAccountBuilder("id2", 2)
+        registerAndEnableAccount(makeQuickAccountBuilder("id2", 2, null)
                 .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER)
                 .setSupportedUriSchemes(Arrays.asList(PhoneAccount.SCHEME_SIP))
                 .build());
 
         assertEquals(1, mRegistrar.getCallCapablePhoneAccounts(PhoneAccount.SCHEME_SIP,
-                false /* includeDisabled */, Process.myUserHandle()).size(),
+                false /* includeDisabled */, Process.myUserHandle(), false).size(),
                 PhoneAccount.CAPABILITY_VIDEO_CALLING);
         assertEquals(2, mRegistrar.getCallCapablePhoneAccounts(PhoneAccount.SCHEME_SIP,
-                false /* includeDisabled */, Process.myUserHandle()).size(), 0 /* none extra */);
+                false /* includeDisabled */, Process.myUserHandle(), false)
+                .size(), 0 /* none extra */);
         assertEquals(0, mRegistrar.getCallCapablePhoneAccounts(PhoneAccount.SCHEME_SIP,
-                false /* includeDisabled */, Process.myUserHandle()).size(),
+                false /* includeDisabled */, Process.myUserHandle(), false).size(),
                 PhoneAccount.CAPABILITY_RTT);
     }
 
@@ -1059,8 +1150,8 @@
         registerAndEnableAccount(pa1);
         registerAndEnableAccount(pa2);
 
-        assertEquals(1, mRegistrar.getAllPhoneAccounts(users.get(0)).size());
-        assertEquals(1, mRegistrar.getAllPhoneAccounts(users.get(1)).size());
+        assertEquals(1, mRegistrar.getAllPhoneAccounts(users.get(0), false).size());
+        assertEquals(1, mRegistrar.getAllPhoneAccounts(users.get(1), false).size());
 
 
         // WHEN
@@ -1254,6 +1345,366 @@
                 defaultPhoneAccountHandle.phoneAccountHandle.getId());
     }
 
+    /**
+     * Test that an {@link IllegalArgumentException} is thrown when a package registers a
+     * {@link PhoneAccountHandle} with a { PhoneAccountHandle#packageName} that is over the
+     * character limit set
+     */
+    @Test
+    public void testInvalidPhoneAccountHandlePackageNameThrowsException() {
+        // GIVEN
+        String invalidPackageName = INVALID_STR;
+        PhoneAccountHandle handle = makeQuickAccountHandle(
+                new ComponentName(invalidPackageName, this.getClass().getName()), TEST_ID);
+        PhoneAccount.Builder builder = makeBuilderWithBindCapabilities(handle);
+
+        // THEN
+        try {
+            PhoneAccount account = builder.build();
+            assertEquals(invalidPackageName,
+                    account.getAccountHandle().getComponentName().getPackageName());
+            mRegistrar.registerPhoneAccount(account);
+            fail("failed to throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // pass test
+        } finally {
+            mRegistrar.unregisterPhoneAccount(handle);
+        }
+    }
+
+    /**
+     * Test that an {@link IllegalArgumentException} is thrown when a package registers a
+     * {@link PhoneAccountHandle} with a { PhoneAccountHandle#className} that is over the
+     * character limit set
+     */
+    @Test
+    public void testInvalidPhoneAccountHandleClassNameThrowsException() {
+        // GIVEN
+        String invalidClassName = INVALID_STR;
+        PhoneAccountHandle handle = makeQuickAccountHandle(
+                new ComponentName(this.getClass().getPackageName(), invalidClassName), TEST_ID);
+        PhoneAccount.Builder builder = makeBuilderWithBindCapabilities(handle);
+
+        // THEN
+        try {
+            PhoneAccount account = builder.build();
+            assertEquals(invalidClassName,
+                    account.getAccountHandle().getComponentName().getClassName());
+            mRegistrar.registerPhoneAccount(account);
+            fail("failed to throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // pass test
+        } finally {
+            mRegistrar.unregisterPhoneAccount(handle);
+        }
+    }
+
+    /**
+     * Test that an {@link IllegalArgumentException} is thrown when a package registers a
+     * {@link PhoneAccountHandle} with a { PhoneAccount#mId} that is over the character limit set
+     */
+    @Test
+    public void testInvalidPhoneAccountHandleIdThrowsException() {
+        // GIVEN
+        String invalidId = INVALID_STR;
+        PhoneAccountHandle handle = makeQuickAccountHandle(invalidId);
+        PhoneAccount.Builder builder = makeBuilderWithBindCapabilities(handle);
+
+        // THEN
+        try {
+            PhoneAccount account = builder.build();
+            assertEquals(invalidId, account.getAccountHandle().getId());
+            mRegistrar.registerPhoneAccount(account);
+            fail("failed to throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // pass test
+        } finally {
+            mRegistrar.unregisterPhoneAccount(handle);
+        }
+    }
+
+    /**
+     * Test that an {@link IllegalArgumentException} is thrown when a package registers a
+     * {@link PhoneAccount} with a { PhoneAccount#mLabel} that is over the character limit set
+     */
+    @Test
+    public void testInvalidLabelThrowsException() {
+        // GIVEN
+        String invalidLabel = INVALID_STR;
+        PhoneAccountHandle handle = makeQuickAccountHandle(TEST_ID);
+        PhoneAccount.Builder builder = new PhoneAccount.Builder(handle, invalidLabel)
+                .setCapabilities(PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS);
+
+        // WHEN
+        when(mAppLabelProxy.getAppLabel(anyString())).thenReturn(invalidLabel);
+
+        // THEN
+        try {
+            PhoneAccount account = builder.build();
+            assertEquals(invalidLabel, account.getLabel());
+            mRegistrar.registerPhoneAccount(account);
+            fail("failed to throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // pass test
+        } finally {
+            mRegistrar.unregisterPhoneAccount(handle);
+        }
+    }
+
+    /**
+     * Test that an {@link IllegalArgumentException} is thrown when a package registers a
+     * {@link PhoneAccount} with a {PhoneAccount#mShortDescription} that is over the character
+     * limit set
+     */
+    @Test
+    public void testInvalidShortDescriptionThrowsException() {
+        // GIVEN
+        String invalidShortDescription = INVALID_STR;
+        PhoneAccountHandle handle = makeQuickAccountHandle(TEST_ID);
+        PhoneAccount.Builder builder = makeBuilderWithBindCapabilities(handle)
+                .setShortDescription(invalidShortDescription);
+
+        // THEN
+        try {
+            PhoneAccount account = builder.build();
+            assertEquals(invalidShortDescription, account.getShortDescription());
+            mRegistrar.registerPhoneAccount(account);
+            fail("failed to throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // pass test
+        } finally {
+            mRegistrar.unregisterPhoneAccount(handle);
+        }
+    }
+
+    /**
+     * Test that an {@link IllegalArgumentException} is thrown when a package registers a
+     * {@link PhoneAccount} with a {PhoneAccount#mGroupId} that is over the character limit set
+     */
+    @Test
+    public void testInvalidGroupIdThrowsException() {
+        // GIVEN
+        String invalidGroupId = INVALID_STR;
+        PhoneAccountHandle handle = makeQuickAccountHandle(TEST_ID);
+        PhoneAccount.Builder builder = makeBuilderWithBindCapabilities(handle)
+                .setGroupId(invalidGroupId);
+
+        // THEN
+        try {
+            PhoneAccount account = builder.build();
+            assertEquals(invalidGroupId, account.getGroupId());
+            mRegistrar.registerPhoneAccount(account);
+            fail("failed to throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // pass test
+        } finally {
+            mRegistrar.unregisterPhoneAccount(handle);
+        }
+    }
+
+    /**
+     * Test that an {@link IllegalArgumentException} is thrown when a package registers a
+     * {@link PhoneAccount} with a {PhoneAccount#mExtras} that is over the character limit set
+     */
+    @Test
+    public void testInvalidExtraStringKeyThrowsException() {
+        // GIVEN
+        String invalidBundleKey = INVALID_STR;
+        String keyValue = "value";
+        Bundle extras = new Bundle();
+        extras.putString(invalidBundleKey, keyValue);
+        PhoneAccountHandle handle = makeQuickAccountHandle(TEST_ID);
+        PhoneAccount.Builder builder = makeBuilderWithBindCapabilities(handle)
+                .setExtras(extras);
+
+        // THEN
+        try {
+            PhoneAccount account = builder.build();
+            assertEquals(keyValue, account.getExtras().getString(invalidBundleKey));
+            mRegistrar.registerPhoneAccount(account);
+            fail("failed to throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // pass test
+        } finally {
+            mRegistrar.unregisterPhoneAccount(handle);
+        }
+    }
+
+    /**
+     * Test that an {@link IllegalArgumentException} is thrown when a package registers a
+     * {@link PhoneAccount} with a {PhoneAccount#mExtras} that is over the character limit set
+     */
+    @Test
+    public void testInvalidExtraStringValueThrowsException() {
+        // GIVEN
+        String extrasKey = "ExtrasStringKey";
+        String invalidBundleValue = INVALID_STR;
+        Bundle extras = new Bundle();
+        extras.putString(extrasKey, invalidBundleValue);
+        PhoneAccountHandle handle = makeQuickAccountHandle(TEST_ID);
+        PhoneAccount.Builder builder = makeBuilderWithBindCapabilities(handle)
+                .setExtras(extras);
+
+        // THEN
+        try {
+            PhoneAccount account = builder.build();
+            assertEquals(invalidBundleValue, account.getExtras().getString(extrasKey));
+            mRegistrar.registerPhoneAccount(account);
+            fail("failed to throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // pass test
+        } finally {
+            mRegistrar.unregisterPhoneAccount(handle);
+        }
+    }
+
+    /**
+     * Test that an {@link IllegalArgumentException} is thrown when a package registers a
+     * {@link PhoneAccount} with a {PhoneAccount#mExtras} that is over the (key,value) pair limit
+     */
+    @Test
+    public void testInvalidExtraElementsExceedsLimitAndThrowsException() {
+        // GIVEN
+        int invalidBundleExtrasLimit =
+                PhoneAccountRegistrar.MAX_PHONE_ACCOUNT_EXTRAS_KEY_PAIR_LIMIT + 1;
+        Bundle extras = new Bundle();
+        for (int i = 0; i < invalidBundleExtrasLimit; i++) {
+            extras.putString(UUID.randomUUID().toString(), "value");
+        }
+        PhoneAccountHandle handle = makeQuickAccountHandle(TEST_ID);
+        PhoneAccount.Builder builder = makeBuilderWithBindCapabilities(handle)
+                .setExtras(extras);
+        // THEN
+        try {
+            PhoneAccount account = builder.build();
+            assertEquals(invalidBundleExtrasLimit, account.getExtras().size());
+            mRegistrar.registerPhoneAccount(account);
+            fail("failed to throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // Test Pass
+        } finally {
+            mRegistrar.unregisterPhoneAccount(handle);
+        }
+    }
+
+    /**
+     * Ensure an IllegalArgumentException is thrown when adding more than 10 schemes for a single
+     * account
+     */
+    @Test
+    public void testLimitOnSchemeCount() {
+        PhoneAccountHandle handle = makeQuickAccountHandle(TEST_ID);
+        PhoneAccount.Builder builder = new PhoneAccount.Builder(handle, TEST_LABEL);
+        for (int i = 0; i < PhoneAccountRegistrar.MAX_PHONE_ACCOUNT_REGISTRATIONS + 1; i++) {
+            builder.addSupportedUriScheme(Integer.toString(i));
+        }
+        try {
+            mRegistrar.enforceLimitsOnSchemes(builder.build());
+            fail("should have hit exception in enforceLimitOnSchemes");
+        } catch (IllegalArgumentException e) {
+            // pass test
+        }
+    }
+
+    /**
+     * Ensure an IllegalArgumentException is thrown when adding more 256 chars for a single
+     * account
+     */
+    @Test
+    public void testLimitOnSchemeLength() {
+        PhoneAccountHandle handle = makeQuickAccountHandle(TEST_ID);
+        PhoneAccount.Builder builder = new PhoneAccount.Builder(handle, TEST_LABEL);
+        builder.addSupportedUriScheme(INVALID_STR);
+        try {
+            mRegistrar.enforceLimitsOnSchemes(builder.build());
+            fail("should have hit exception in enforceLimitOnSchemes");
+        } catch (IllegalArgumentException e) {
+            // pass test
+        }
+    }
+
+    /**
+     * Ensure an IllegalArgumentException is thrown when adding an address over the limit
+     */
+    @Test
+    public void testLimitOnAddress() {
+        String text = "a".repeat(100);
+        PhoneAccountHandle handle = makeQuickAccountHandle(TEST_ID);
+        PhoneAccount.Builder builder = makeBuilderWithBindCapabilities(handle)
+                .setAddress(Uri.fromParts(text, text, text));
+        try {
+            mRegistrar.registerPhoneAccount(builder.build());
+            fail("failed to throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // pass test
+        }
+        finally {
+            mRegistrar.unregisterPhoneAccount(handle);
+        }
+    }
+
+    /**
+     * Ensure an IllegalArgumentException is thrown when an Icon that throws an IOException is given
+     */
+    @Test
+    public void testLimitOnIcon() throws Exception {
+        Icon mockIcon = mock(Icon.class);
+        // GIVEN
+        PhoneAccount.Builder builder = makeBuilderWithBindCapabilities(
+                makeQuickAccountHandle(TEST_ID)).setIcon(mockIcon);
+        try {
+            // WHEN
+            Mockito.doThrow(new IOException())
+                    .when(mockIcon).writeToStream(any(OutputStream.class));
+            //THEN
+            mRegistrar.enforceIconSizeLimit(builder.build());
+            fail("failed to throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // pass test
+            assertTrue(e.getMessage().contains(PhoneAccountRegistrar.ICON_ERROR_MSG));
+        }
+    }
+
+    /**
+     * Ensure an IllegalArgumentException is thrown when providing a SubscriptionAddress that
+     * exceeds the PhoneAccountRegistrar limit.
+     */
+    @Test
+    public void testLimitOnSubscriptionAddress() throws Exception {
+        String text = "a".repeat(100);
+        PhoneAccount.Builder builder =  new PhoneAccount.Builder(makeQuickAccountHandle(TEST_ID),
+                TEST_LABEL).setSubscriptionAddress(Uri.fromParts(text, text, text));
+        try {
+            mRegistrar.enforceCharacterLimit(builder.build());
+            fail("failed to throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // pass test
+        }
+    }
+
+    /**
+     * PhoneAccounts with CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS do not require a
+     * ConnectionService. Ensure that such an account can be registered and fetched.
+     */
+    @Test
+    public void testFetchingTransactionalAccounts() {
+        PhoneAccount account = makeBuilderWithBindCapabilities(
+                makeQuickAccountHandle(TEST_ID)).build();
+
+        try {
+            assertEquals(0, mRegistrar.getAllPhoneAccounts(null, true).size());
+            registerAndEnableAccount(account);
+            assertEquals(1, mRegistrar.getAllPhoneAccounts(null, true).size());
+        } finally {
+            mRegistrar.unregisterPhoneAccount(account.getAccountHandle());
+        }
+    }
+
+    private static PhoneAccount.Builder makeBuilderWithBindCapabilities(PhoneAccountHandle handle) {
+        return new PhoneAccount.Builder(handle, TEST_LABEL)
+                .setCapabilities(PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS);
+    }
+
     private static ComponentName makeQuickConnectionServiceComponentName() {
         return new ComponentName(
                 "com.android.server.telecom.tests",
@@ -1268,9 +1719,17 @@
         return new PhoneAccountHandle(name, id, Process.myUserHandle());
     }
 
-    private PhoneAccount.Builder makeQuickAccountBuilder(String id, int idx) {
+    private static PhoneAccountHandle makeQuickAccountHandleForUser(
+            String id, UserHandle userHandle) {
+        return new PhoneAccountHandle(makeQuickConnectionServiceComponentName(), id, userHandle);
+    }
+
+    private PhoneAccount.Builder makeQuickAccountBuilder(
+            String id, int idx, UserHandle userHandle) {
         return new PhoneAccount.Builder(
-                makeQuickAccountHandle(id),
+                userHandle == null
+                        ? makeQuickAccountHandle(id)
+                        : makeQuickAccountHandleForUser(id, userHandle),
                 "label" + idx);
     }
 
@@ -1292,7 +1751,7 @@
     }
 
     private PhoneAccount makeQuickAccount(String id, int idx) {
-        return makeQuickAccountBuilder(id, idx)
+        return makeQuickAccountBuilder(id, idx, null)
                 .setAddress(Uri.parse("http://foo.com/" + idx))
                 .setSubscriptionAddress(Uri.parse("tel:555-000" + idx))
                 .setCapabilities(idx)
@@ -1309,7 +1768,7 @@
      */
     private PhoneAccount makeQuickSimAccount(int simId) {
         PhoneAccount simAccount =
-                makeQuickAccountBuilder("sim" + simId, simId)
+                makeQuickAccountBuilder("sim" + simId, simId, null)
                         .setCapabilities(
                                 PhoneAccount.CAPABILITY_CALL_PROVIDER
                                         | PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
diff --git a/tests/src/com/android/server/telecom/tests/RingerTest.java b/tests/src/com/android/server/telecom/tests/RingerTest.java
index 12420a8..a4adf77 100644
--- a/tests/src/com/android/server/telecom/tests/RingerTest.java
+++ b/tests/src/com/android/server/telecom/tests/RingerTest.java
@@ -27,14 +27,17 @@
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 import android.app.NotificationManager;
+import android.content.ComponentName;
 import android.content.Context;
 import android.media.AudioAttributes;
 import android.media.AudioManager;
@@ -42,10 +45,12 @@
 import android.media.VolumeShaper;
 import android.net.Uri;
 import android.os.Bundle;
-import android.os.Parcel;
+import android.os.UserHandle;
+import android.os.UserManager;
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
+import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
 import android.test.suitebuilder.annotation.SmallTest;
 
@@ -66,69 +71,39 @@
 import org.mockito.Mock;
 import org.mockito.Spy;
 
-import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
 
 @RunWith(JUnit4.class)
 public class RingerTest extends TelecomTestCase {
     private static final Uri FAKE_RINGTONE_URI = Uri.parse("content://media/fake/audio/1729");
-    private static class UriVibrationEffect extends VibrationEffect {
-        final Uri mUri;
-
-        private UriVibrationEffect(Uri uri) {
-            mUri = uri;
-        }
-
-        @Override
-        public VibrationEffect resolve(int defaultAmplitude) {
-            return this;
-        }
-
-        @Override
-        public VibrationEffect scale(float scaleFactor) {
-            return this;
-        }
-
-        @Override
-        public void validate() {
-            // not needed
-        }
-
-        @Override
-        public long getDuration() {
-            return 0;
-        }
-
-        @Override
-        public void writeToParcel(Parcel dest, int flags) {
-            // not needed
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) return true;
-            if (o == null || getClass() != o.getClass()) return false;
-            UriVibrationEffect that = (UriVibrationEffect) o;
-            return Objects.equals(mUri, that.mUri);
-        }
-    }
+    // Returned when the a URI-based VibrationEffect is attempted, to avoid depending on actual
+    // device configuration for ringtone URIs. The actual Uri can be verified via the
+    // VibrationEffectProxy mock invocation.
+    private static final VibrationEffect URI_VIBRATION_EFFECT =
+            VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK);
 
     @Mock InCallTonePlayer.Factory mockPlayerFactory;
     @Mock SystemSettingsUtil mockSystemSettingsUtil;
-    @Mock AsyncRingtonePlayer mockRingtonePlayer;
     @Mock RingtoneFactory mockRingtoneFactory;
     @Mock Vibrator mockVibrator;
     @Mock InCallController mockInCallController;
     @Mock NotificationManager mockNotificationManager;
+    @Mock Ringer.AccessibilityManagerAdapter mockAccessibilityManagerAdapter;
+
     @Spy Ringer.VibrationEffectProxy spyVibrationEffectProxy;
 
     @Mock InCallTonePlayer mockTonePlayer;
     @Mock Call mockCall1;
     @Mock Call mockCall2;
 
+    private static final PhoneAccountHandle PA_HANDLE =
+            new PhoneAccountHandle(new ComponentName("pa_pkg", "pa_cls"),
+                    "pa_id");
+
+    boolean mIsHapticPlaybackSupported = true;  // Note: initializeRinger() after changes.
+    AsyncRingtonePlayer asyncRingtonePlayer = new AsyncRingtonePlayer();
     Ringer mRingerUnderTest;
     AudioManager mockAudioManager;
-    CompletableFuture<Boolean> mFuture = new CompletableFuture<>();
     CompletableFuture<Void> mRingCompletionFuture = new CompletableFuture<>();
 
     @Override
@@ -136,28 +111,33 @@
     public void setUp() throws Exception {
         super.setUp();
         mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
-        doAnswer(invocation -> {
-            Uri ringtoneUriForEffect = invocation.getArgument(0);
-            return new UriVibrationEffect(ringtoneUriForEffect);
-        }).when(spyVibrationEffectProxy).get(any(), any());
+        doReturn(URI_VIBRATION_EFFECT).when(spyVibrationEffectProxy).get(any(), any());
         when(mockPlayerFactory.createPlayer(anyInt())).thenReturn(mockTonePlayer);
-        mockAudioManager =
-                (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        mockAudioManager = mContext.getSystemService(AudioManager.class);
         when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
-        when(mockSystemSettingsUtil.isHapticPlaybackSupported(any(Context.class))).thenReturn(true);
-        mockNotificationManager =
-                (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+        when(mockSystemSettingsUtil.isHapticPlaybackSupported(any(Context.class)))
+                .thenAnswer((invocation) -> mIsHapticPlaybackSupported);
+        mockNotificationManager =mContext.getSystemService(NotificationManager.class);
         when(mockTonePlayer.startTone()).thenReturn(true);
         when(mockNotificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(true);
         when(mockRingtoneFactory.hasHapticChannels(any(Ringtone.class))).thenReturn(false);
-        when(mockRingtonePlayer.play(any(RingtoneFactory.class), any(Call.class),
-                nullable(VolumeShaper.Configuration.class), anyBoolean(), anyBoolean()))
-                .thenReturn(mFuture);
-        mRingerUnderTest = new Ringer(mockPlayerFactory, mContext, mockSystemSettingsUtil,
-                mockRingtonePlayer, mockRingtoneFactory, mockVibrator, spyVibrationEffectProxy,
-                mockInCallController);
         when(mockCall1.getState()).thenReturn(CallState.RINGING);
         when(mockCall2.getState()).thenReturn(CallState.RINGING);
+        when(mockCall1.getAssociatedUser()).thenReturn(PA_HANDLE.getUserHandle());
+        when(mockCall2.getAssociatedUser()).thenReturn(PA_HANDLE.getUserHandle());
+
+        createRingerUnderTest();
+    }
+
+    /**
+     * (Re-)Creates the Ringer for the test. This needs to be called if changing final properties,
+     * like mIsHapticPlaybackSupported.
+     */
+    private void createRingerUnderTest() {
+        mRingerUnderTest = new Ringer(mockPlayerFactory, mContext, mockSystemSettingsUtil,
+                asyncRingtonePlayer, mockRingtoneFactory, mockVibrator, spyVibrationEffectProxy,
+                mockInCallController, mockNotificationManager, mockAccessibilityManagerAdapter);
+        // This future is used to wait for AsyncRingtonePlayer to finish its part.
         mRingerUnderTest.setBlockOnRingingFuture(mRingCompletionFuture);
     }
 
@@ -169,33 +149,30 @@
 
     @SmallTest
     @Test
-    public void testNoActionInTheaterMode() {
+    public void testNoActionInTheaterMode() throws Exception {
         // Start call waiting to make sure that it doesn't stop when we start ringing
-        mFuture.complete(false); // not using audio coupled haptics
         mRingerUnderTest.startCallWaiting(mockCall1);
         when(mockSystemSettingsUtil.isTheaterModeOn(any(Context.class))).thenReturn(true);
-        assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
+        assertFalse(startRingingAndWaitForAsync(mockCall2, false));
+        verifyZeroInteractions(mockRingtoneFactory);
         verify(mockTonePlayer, never()).stopTone();
-        verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class),
-                nullable(VolumeShaper.Configuration.class), anyBoolean(), anyBoolean());
         verify(mockVibrator, never())
                 .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
     }
 
     @SmallTest
     @Test
-    public void testNoActionWithExternalRinger() {
-        mFuture.complete(false); // not using audio coupled haptics
+    public void testNoActionWithExternalRinger() throws Exception {
         Bundle externalRingerExtra = new Bundle();
         externalRingerExtra.putBoolean(TelecomManager.EXTRA_CALL_HAS_IN_BAND_RINGTONE, true);
         when(mockCall1.getIntentExtras()).thenReturn(externalRingerExtra);
         when(mockCall2.getIntentExtras()).thenReturn(externalRingerExtra);
         // Start call waiting to make sure that it doesn't stop when we start ringing
         mRingerUnderTest.startCallWaiting(mockCall1);
-        assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
+        assertFalse(startRingingAndWaitForAsync(mockCall2, false));
+
+        verifyZeroInteractions(mockRingtoneFactory);
         verify(mockTonePlayer, never()).stopTone();
-        verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class),
-                nullable(VolumeShaper.Configuration.class), anyBoolean(), anyBoolean());
         verify(mockVibrator, never())
                 .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
     }
@@ -203,15 +180,15 @@
     @SmallTest
     @Test
     public void testNoActionWhenDialerRings() throws Exception {
-        mFuture.complete(false); // not using audio coupled haptics
         // Start call waiting to make sure that it doesn't stop when we start ringing
         mRingerUnderTest.startCallWaiting(mockCall1);
-        when(mockInCallController.doesConnectedDialerSupportRinging()).thenReturn(true);
-        assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
-        mRingCompletionFuture.get();
+        when(mockInCallController.doesConnectedDialerSupportRinging(
+                any(UserHandle.class))).thenReturn(true);
+        ensureRingerIsNotAudible();
+        assertFalse(startRingingAndWaitForAsync(mockCall2, false));
+
+        verifyZeroInteractions(mockRingtoneFactory);
         verify(mockTonePlayer, never()).stopTone();
-        verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class),
-                nullable(VolumeShaper.Configuration.class), anyBoolean(), anyBoolean());
         verify(mockVibrator, never())
                 .vibrate(any(VibrationEffect.class), any(AudioAttributes.class));
     }
@@ -219,49 +196,46 @@
     @SmallTest
     @Test
     public void testAudioFocusStillAcquiredWhenDialerRings() throws Exception {
-        mFuture.complete(false); // not using audio coupled haptics
+
         // Start call waiting to make sure that it doesn't stop when we start ringing
         mRingerUnderTest.startCallWaiting(mockCall1);
-        when(mockInCallController.doesConnectedDialerSupportRinging()).thenReturn(true);
+        when(mockInCallController.doesConnectedDialerSupportRinging(
+                any(UserHandle.class))).thenReturn(true);
         ensureRingerIsAudible();
-        assertTrue(mRingerUnderTest.startRinging(mockCall2, false));
-        mRingCompletionFuture.get();
+        assertTrue(startRingingAndWaitForAsync(mockCall2, false));
+        verifyZeroInteractions(mockRingtoneFactory);
         verify(mockTonePlayer, never()).stopTone();
-        verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class),
-                nullable(VolumeShaper.Configuration.class), anyBoolean(), anyBoolean());
         verify(mockVibrator, never())
                 .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
     }
 
     @SmallTest
     @Test
-    public void testNoActionWhenCallIsSelfManaged() {
-        mFuture.complete(false); // not using audio coupled haptics
+    public void testNoActionWhenCallIsSelfManaged() throws Exception {
         // Start call waiting to make sure that it doesn't stop when we start ringing
         mRingerUnderTest.startCallWaiting(mockCall1);
         when(mockCall2.isSelfManaged()).thenReturn(true);
         // We do want to acquire audio focus when self-managed
-        assertTrue(mRingerUnderTest.startRinging(mockCall2, true));
+        assertTrue(startRingingAndWaitForAsync(mockCall2, true));
+
+        verifyZeroInteractions(mockRingtoneFactory);
         verify(mockTonePlayer, never()).stopTone();
-        verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class),
-                nullable(VolumeShaper.Configuration.class), anyBoolean(), anyBoolean());
         verify(mockVibrator, never())
                 .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
     }
 
     @SmallTest
     @Test
-    public void testCallWaitingButNoRingForSpecificContacts() {
-        mFuture.complete(false); // not using audio coupled haptics
+    public void testCallWaitingButNoRingForSpecificContacts() throws Exception {
         when(mockNotificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(false);
         // Start call waiting to make sure that it does stop when we start ringing
         mRingerUnderTest.startCallWaiting(mockCall1);
         verify(mockTonePlayer).startTone();
 
-        assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
+        assertFalse(startRingingAndWaitForAsync(mockCall2, false));
+
+        verifyZeroInteractions(mockRingtoneFactory);
         verify(mockTonePlayer).stopTone();
-        verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class),
-                nullable(VolumeShaper.Configuration.class), anyBoolean(), anyBoolean());
         verify(mockVibrator, never())
                 .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
     }
@@ -269,16 +243,19 @@
     @SmallTest
     @Test
     public void testNoVibrateDueToAudioCoupledHaptics() throws Exception {
+        Ringtone mockRingtone = ensureRingtoneMocked();
+
         mRingerUnderTest.startCallWaiting(mockCall1);
         ensureRingerIsAudible();
         enableVibrationWhenRinging();
         // Pretend we're using audio coupled haptics.
-        mFuture.complete(true);
-        assertTrue(mRingerUnderTest.startRinging(mockCall2, false));
-        mRingCompletionFuture.get();
+        setIsUsingHaptics(mockRingtone, true);
+        assertTrue(startRingingAndWaitForAsync(mockCall1, false));
+        verify(mockRingtoneFactory, times(1))
+            .getRingtone(any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean());
+        verifyNoMoreInteractions(mockRingtoneFactory);
         verify(mockTonePlayer).stopTone();
-        verify(mockRingtonePlayer).play(any(RingtoneFactory.class), any(Call.class), isNull(),
-                eq(true) /* isRingerAudible */, eq(true) /* isVibrationEnabled */);
+        verify(mockRingtone).play();
         verify(mockVibrator, never()).vibrate(any(VibrationEffect.class),
                 any(VibrationAttributes.class));
     }
@@ -286,17 +263,24 @@
     @SmallTest
     @Test
     public void testVibrateButNoRingForNullRingtone() throws Exception {
+        when(mockRingtoneFactory.getRingtone(
+                 any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean()))
+            .thenReturn(null);
+
         mRingerUnderTest.startCallWaiting(mockCall1);
-        when(mockRingtoneFactory.getRingtone(any(Call.class))).thenReturn(null);
         when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
-        mFuture.complete(false); // not using audio coupled haptics
         enableVibrationWhenRinging();
-        assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
-        mRingCompletionFuture.get();
+        // The ringtone isn't known to be null until the async portion after the call completes,
+        // so startRinging still returns true here as there should nominally be a ringtone.
+        // Notably, vibration still happens in this scenario.
+        assertTrue(startRingingAndWaitForAsync(mockCall2, false));
         verify(mockTonePlayer).stopTone();
-        // Try to play a silent haptics ringtone
-        verify(mockRingtonePlayer).play(any(RingtoneFactory.class), any(Call.class), isNull(),
-                eq(false) /* isRingerAudible */, eq(true) /* isVibrationEnabled */);
+
+        // Just the one call to mockRingtoneFactory, which returned null.
+        verify(mockRingtoneFactory).getRingtone(
+                any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean());
+        verifyNoMoreInteractions(mockRingtoneFactory);
+
         // Play default vibration when future completes with no audio coupled haptics
         verify(mockVibrator).vibrate(eq(mRingerUnderTest.mDefaultVibrationEffect),
                 any(VibrationAttributes.class));
@@ -305,19 +289,39 @@
     @SmallTest
     @Test
     public void testVibrateButNoRingForSilentRingtone() throws Exception {
+        Ringtone mockRingtone = ensureRingtoneMocked();
+
         mRingerUnderTest.startCallWaiting(mockCall1);
-        Ringtone mockRingtone = mock(Ringtone.class);
-        when(mockRingtoneFactory.getRingtone(any(Call.class))).thenReturn(mockRingtone);
+        when(mockRingtoneFactory.getRingtone(any(Call.class), eq(null), anyBoolean()))
+            .thenReturn(mockRingtone);
         when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
         when(mockAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(0);
-        mFuture.complete(false); // not using audio coupled haptics
         enableVibrationWhenRinging();
-        assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
-        mRingCompletionFuture.get();
+        assertFalse(startRingingAndWaitForAsync(mockCall2, false));
         verify(mockTonePlayer).stopTone();
         // Try to play a silent haptics ringtone
-        verify(mockRingtonePlayer).play(any(RingtoneFactory.class), any(Call.class), isNull(),
-                eq(false) /* isRingerAudible */, eq(true) /* isVibrationEnabled */);
+        verify(mockRingtoneFactory, times(1)).getHapticOnlyRingtone();
+        verifyNoMoreInteractions(mockRingtoneFactory);
+        verify(mockRingtone).play();
+
+        // Play default vibration when future completes with no audio coupled haptics
+        verify(mockVibrator).vibrate(eq(mRingerUnderTest.mDefaultVibrationEffect),
+                any(VibrationAttributes.class));
+    }
+
+    @SmallTest
+    @Test
+    public void testVibrateButNoRingForSilentRingtoneWithoutAudioHapticSupport() throws Exception {
+        mIsHapticPlaybackSupported = false;
+        createRingerUnderTest();  // Needed after changing haptic playback support.
+        mRingerUnderTest.startCallWaiting(mockCall1);
+        when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
+        when(mockAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(0);
+        enableVibrationWhenRinging();
+        assertFalse(startRingingAndWaitForAsync(mockCall2, false));
+        verify(mockTonePlayer).stopTone();
+        verifyZeroInteractions(mockRingtoneFactory);
+
         // Play default vibration when future completes with no audio coupled haptics
         verify(mockVibrator).vibrate(eq(mRingerUnderTest.mDefaultVibrationEffect),
                 any(VibrationAttributes.class));
@@ -326,19 +330,20 @@
     @SmallTest
     @Test
     public void testAudioCoupledHapticsForSilentRingtone() throws Exception {
+        Ringtone mockRingtone = ensureRingtoneMocked();
+
         mRingerUnderTest.startCallWaiting(mockCall1);
-        Ringtone mockRingtone = mock(Ringtone.class);
-        when(mockRingtoneFactory.getRingtone(any(Call.class))).thenReturn(mockRingtone);
         when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_VIBRATE);
         when(mockAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(0);
-        mFuture.complete(true); // using audio coupled haptics
+        setIsUsingHaptics(mockRingtone, true);
         enableVibrationWhenRinging();
-        assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
-        mRingCompletionFuture.get();
+        assertFalse(startRingingAndWaitForAsync(mockCall2, false));
+
+        verify(mockRingtoneFactory, times(1)).getHapticOnlyRingtone();
+        verifyNoMoreInteractions(mockRingtoneFactory);
         verify(mockTonePlayer).stopTone();
         // Try to play a silent haptics ringtone
-        verify(mockRingtonePlayer).play(any(RingtoneFactory.class), any(Call.class), isNull(),
-                eq(false) /* isRingerAudible */, eq(true) /* isVibrationEnabled */);
+        verify(mockRingtone).play();
         // Skip vibration for audio coupled haptics
         verify(mockVibrator, never()).vibrate(any(VibrationEffect.class),
                 any(VibrationAttributes.class));
@@ -346,60 +351,36 @@
 
     @SmallTest
     @Test
-    public void testStopRingingBeforeHapticsLookupComplete() throws Exception {
-        enableVibrationWhenRinging();
-        Ringtone mockRingtone = mock(Ringtone.class);
-        when(mockRingtoneFactory.getRingtone(nullable(Call.class))).thenReturn(mockRingtone);
-        when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
-
-        mRingerUnderTest.startRinging(mockCall1, false);
-        // Make sure we haven't started the vibrator yet, but have started ringing.
-        verify(mockRingtonePlayer).play(nullable(RingtoneFactory.class), nullable(Call.class),
-                nullable(VolumeShaper.Configuration.class), anyBoolean(), anyBoolean());
-        verify(mockVibrator, never()).vibrate(nullable(VibrationEffect.class),
-                nullable(VibrationAttributes.class));
-        // Simulate something stopping the ringer
-        mRingerUnderTest.stopRinging();
-        verify(mockRingtonePlayer).stop();
-        verify(mockVibrator, never()).cancel();
-        // Simulate the haptics computation finishing
-        mFuture.complete(false);
-        // Then make sure that we don't actually start vibrating.
-        verify(mockVibrator, never()).vibrate(nullable(VibrationEffect.class),
-                nullable(VibrationAttributes.class));
-    }
-
-    @SmallTest
-    @Test
     public void testCustomVibrationForRingtone() throws Exception {
         mRingerUnderTest.startCallWaiting(mockCall1);
-        Ringtone mockRingtone = mock(Ringtone.class);
+        Ringtone mockRingtone = ensureRingtoneMocked();
         when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
-        when(mockRingtoneFactory.getRingtone(any(Call.class))).thenReturn(mockRingtone);
         when(mockRingtone.getUri()).thenReturn(FAKE_RINGTONE_URI);
-        mFuture.complete(false); // not using audio coupled haptics
         enableVibrationWhenRinging();
-        assertTrue(mRingerUnderTest.startRinging(mockCall2, false));
-        mRingCompletionFuture.get();
+        assertTrue(startRingingAndWaitForAsync(mockCall2, false));
         verify(mockTonePlayer).stopTone();
-        verify(mockRingtonePlayer).play(any(RingtoneFactory.class), any(Call.class), eq(null),
-                eq(true) /* isRingerAudible */, eq(true) /* isVibrationEnabled */);
-        verify(mockVibrator).vibrate(eq(spyVibrationEffectProxy.get(FAKE_RINGTONE_URI, mContext)),
-                any(VibrationAttributes.class));
+        verify(mockRingtoneFactory, times(1))
+            .getRingtone(any(Call.class), isNull(), anyBoolean());
+        verifyNoMoreInteractions(mockRingtoneFactory);
+        verify(mockRingtone).play();
+        verify(spyVibrationEffectProxy).get(eq(FAKE_RINGTONE_URI), any(Context.class));
+        verify(mockVibrator).vibrate(eq(URI_VIBRATION_EFFECT), any(VibrationAttributes.class));
     }
 
     @SmallTest
     @Test
     public void testRingAndNoVibrate() throws Exception {
+        Ringtone mockRingtone = ensureRingtoneMocked();
+
         mRingerUnderTest.startCallWaiting(mockCall1);
         ensureRingerIsAudible();
-        mFuture.complete(false); // not using audio coupled haptics
         enableVibrationOnlyWhenNotRinging();
-        assertTrue(mRingerUnderTest.startRinging(mockCall2, false));
-        mRingCompletionFuture.get();
+        assertTrue(startRingingAndWaitForAsync(mockCall2, false));
+        verify(mockRingtoneFactory, times(1))
+            .getRingtone(any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean());
+        verifyNoMoreInteractions(mockRingtoneFactory);
         verify(mockTonePlayer).stopTone();
-        verify(mockRingtonePlayer).play(any(RingtoneFactory.class), any(Call.class), eq(null),
-                eq(true) /* isRingerAudible */, eq(false) /* isVibrationEnabled */);
+        verify(mockRingtone).play();
         verify(mockVibrator, never())
                 .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
     }
@@ -407,52 +388,31 @@
     @SmallTest
     @Test
     public void testRingWithRampingRinger() throws Exception {
+        Ringtone mockRingtone = ensureRingtoneMocked();
+
         mRingerUnderTest.startCallWaiting(mockCall1);
         ensureRingerIsAudible();
         enableRampingRinger();
-        mFuture.complete(false); // not using audio coupled haptics
         enableVibrationWhenRinging();
-        assertTrue(mRingerUnderTest.startRinging(mockCall2, false));
-        mRingCompletionFuture.get();
+        assertTrue(startRingingAndWaitForAsync(mockCall2, false));
+        verify(mockRingtoneFactory, times(1))
+            .getRingtone(any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean());
+        verifyNoMoreInteractions(mockRingtoneFactory);
         verify(mockTonePlayer).stopTone();
-        verify(mockRingtonePlayer).play(
-            any(RingtoneFactory.class), any(Call.class), any(VolumeShaper.Configuration.class),
-                eq(true) /* isRingerAudible */, eq(true) /* isVibrationEnabled */);
+        verify(mockRingtone).play();
     }
 
     @SmallTest
     @Test
-    public void testSilentRingWithHfpStillAcquiresFocus1() throws Exception {
+    public void testSilentRingWithHfpStillAcquiresFocus() throws Exception {
         mRingerUnderTest.startCallWaiting(mockCall1);
-        Ringtone mockRingtone = mock(Ringtone.class);
-        when(mockRingtoneFactory.getRingtone(any(Call.class))).thenReturn(mockRingtone);
         when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
         when(mockAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(0);
-        mFuture.complete(false); // not using audio coupled haptics
         enableVibrationOnlyWhenNotRinging();
-        assertTrue(mRingerUnderTest.startRinging(mockCall2, true));
-        mRingCompletionFuture.get();
+        assertTrue(startRingingAndWaitForAsync(mockCall2, true));
         verify(mockTonePlayer).stopTone();
-        verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class),
-                nullable(VolumeShaper.Configuration.class), anyBoolean(), anyBoolean());
-        verify(mockVibrator, never())
-                .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
-    }
-
-    @SmallTest
-    @Test
-    public void testSilentRingWithHfpStillAcquiresFocus2() throws Exception {
-        mRingerUnderTest.startCallWaiting(mockCall1);
-        when(mockRingtoneFactory.getRingtone(any(Call.class))).thenReturn(null);
-        when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
-        when(mockAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(0);
-        mFuture.complete(false); // not using audio coupled haptics
-        enableVibrationOnlyWhenNotRinging();
-        assertTrue(mRingerUnderTest.startRinging(mockCall2, true));
-        mRingCompletionFuture.get();
-        verify(mockTonePlayer).stopTone();
-        verify(mockRingtonePlayer, never()).play(any(RingtoneFactory.class), any(Call.class),
-                nullable(VolumeShaper.Configuration.class), anyBoolean(), anyBoolean());
+        // Ringer not audible, so never tries to create a ringtone.
+        verifyZeroInteractions(mockRingtoneFactory);
         verify(mockVibrator, never())
                 .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
     }
@@ -461,27 +421,179 @@
     @Test
     public void testRingAndVibrateForAllowedCallInDndMode() throws Exception {
         mRingerUnderTest.startCallWaiting(mockCall1);
-        Ringtone mockRingtone = mock(Ringtone.class);
-        when(mockRingtoneFactory.getRingtone(any(Call.class))).thenReturn(mockRingtone);
+        Ringtone mockRingtone = ensureRingtoneMocked();
         when(mockNotificationManager.getZenMode()).thenReturn(ZEN_MODE_IMPORTANT_INTERRUPTIONS);
         when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_SILENT);
         when(mockAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(100);
-        mFuture.complete(true); // using audio coupled haptics
         enableVibrationWhenRinging();
-        assertTrue(mRingerUnderTest.startRinging(mockCall2, true));
-        mRingCompletionFuture.get();
+        assertTrue(startRingingAndWaitForAsync(mockCall2, true));
+        verify(mockRingtoneFactory, times(1))
+            .getRingtone(any(Call.class), isNull(), anyBoolean());
+        verifyNoMoreInteractions(mockRingtoneFactory);
         verify(mockTonePlayer).stopTone();
-        verify(mockRingtonePlayer).play(any(RingtoneFactory.class), any(Call.class), eq(null),
-                eq(true) /* isRingerAudible */, eq(true) /* isVibrationEnabled */);
+        verify(mockRingtone).play();
+    }
+
+    /**
+     * test shouldRingForContact will suppress the incoming call if matchesCallFilter returns
+     * false (meaning DND is ON and the caller cannot bypass the settings)
+     */
+    @Test
+    public void testShouldRingForContact_CallSuppressed() {
+        // WHEN
+        when(mockCall1.wasDndCheckComputedForCall()).thenReturn(false);
+        when(mockCall1.getHandle()).thenReturn(Uri.parse(""));
+        when(mContext.getSystemService(NotificationManager.class)).thenReturn(
+                mockNotificationManager);
+        // suppress the call
+        when(mockNotificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(false);
+
+        // run the method under test
+        assertFalse(mRingerUnderTest.shouldRingForContact(mockCall1));
+
+        // THEN
+        // verify we never set the call object and matchesCallFilter is called
+        verify(mockCall1, never()).setCallIsSuppressedByDoNotDisturb(true);
+        verify(mockNotificationManager, times(1))
+                .matchesCallFilter(any(Bundle.class));
+    }
+
+    /**
+     * test shouldRingForContact will alert the user of an incoming call if matchesCallFilter
+     * returns true (meaning DND is NOT suppressing the caller)
+     */
+    @Test
+    public void testShouldRingForContact_CallShouldRing() {
+        // WHEN
+        when(mockCall1.wasDndCheckComputedForCall()).thenReturn(false);
+        when(mockCall1.getHandle()).thenReturn(Uri.parse(""));
+        // alert the user of the call
+        when(mockNotificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(true);
+
+        // run the method under test
+        assertTrue(mRingerUnderTest.shouldRingForContact(mockCall1));
+
+        // THEN
+        // verify we never set the call object and matchesCallFilter is called
+        verify(mockCall1, never()).setCallIsSuppressedByDoNotDisturb(false);
+        verify(mockNotificationManager, times(1))
+                .matchesCallFilter(any(Bundle.class));
+    }
+
+    /**
+     * ensure Telecom does not re-query the NotificationManager if the call object already has
+     * the result.
+     */
+    @Test
+    public void testShouldRingForContact_matchesCallFilterIsAlreadyComputed() {
+        // WHEN
+        when(mockCall1.wasDndCheckComputedForCall()).thenReturn(true);
+        when(mockCall1.isCallSuppressedByDoNotDisturb()).thenReturn(true);
+
+        // THEN
+        assertFalse(mRingerUnderTest.shouldRingForContact(mockCall1));
+        verify(mockCall1, never()).setCallIsSuppressedByDoNotDisturb(false);
+        verify(mockNotificationManager, never()).matchesCallFilter(any(Bundle.class));
+    }
+
+    @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 {
+        Ringtone mockRingtone = mock(Ringtone.class);
+        when(mockRingtoneFactory.getRingtone(
+                any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean()))
+                .thenAnswer(x -> {
+                    // Be slow to create ringtone.
+                    try {
+                        Thread.sleep(300);
+                    } catch (InterruptedException e) {
+                        Thread.currentThread().interrupt();
+                    }
+                    return mockRingtone;
+                });
+        // Start call waiting to make sure that it doesn't stop when we start ringing
+        enableVibrationWhenRinging();
+        mRingerUnderTest.startCallWaiting(mockCall1);
+        when(mockCall2.wasDndCheckComputedForCall()).thenReturn(false);
+        when(mockCall2.getHandle()).thenReturn(Uri.parse(""));
+        when(mockNotificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(true);
+
+        assertTrue(mRingerUnderTest.shouldRingForContact(mockCall2));
+        assertTrue(mRingerUnderTest.startRinging(mockCall2, false));
+        mRingerUnderTest.stopRinging();
+        verify(mockAccessibilityManagerAdapter, atLeastOnce())
+                .stopFlashNotificationSequence(any(Context.class));
+        mRingCompletionFuture.get();  // Don't leak async work.
+        verify(mockVibrator, never())  // cancelled before it started.
+                .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
+    }
+
+    @SmallTest
+    @Test
+    public void testNoRingingForQuietProfile() throws Exception {
+        UserManager um = mContext.getSystemService(UserManager.class);
+        when(um.isManagedProfile(PA_HANDLE.getUserHandle().getIdentifier())).thenReturn(true);
+        when(um.isQuietModeEnabled(PA_HANDLE.getUserHandle())).thenReturn(true);
+        // We don't want to acquire audio focus when self-managed
+        assertFalse(startRingingAndWaitForAsync(mockCall2, true));
+
+        verify(mockTonePlayer, never()).stopTone();
+        verifyZeroInteractions(mockRingtoneFactory);
+        verify(mockVibrator, never())
+                .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
+    }
+
+    /**
+     * Call startRinging and wait for its effects to have played out, to allow reliable assertions
+     * after it. The effects are generally "start playing ringtone" and "start vibration" - not
+     * waiting for anything open-ended.
+     */
+    private boolean startRingingAndWaitForAsync(Call mockCall2, boolean isHfpDeviceAttached)
+            throws Exception {
+        boolean result = mRingerUnderTest.startRinging(mockCall2, isHfpDeviceAttached);
+        mRingCompletionFuture.get();
+        return result;
     }
 
     private void ensureRingerIsAudible() {
-        Ringtone mockRingtone = mock(Ringtone.class);
-        when(mockRingtoneFactory.getRingtone(any(Call.class))).thenReturn(mockRingtone);
         when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
         when(mockAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(100);
     }
 
+    private void ensureRingerIsNotAudible() {
+        when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
+        when(mockAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(0);
+    }
+
     private void enableVibrationWhenRinging() {
         when(mockVibrator.hasVibrator()).thenReturn(true);
         when(mockSystemSettingsUtil.isRingVibrationEnabled(any(Context.class))).thenReturn(true);
@@ -495,4 +607,21 @@
     private void enableRampingRinger() {
         when(mockSystemSettingsUtil.isRampingRingerEnabled(any(Context.class))).thenReturn(true);
     }
+
+    private void setIsUsingHaptics(Ringtone mockRingtone, boolean useHaptics) {
+        // Note: using haptics can also depend on mIsHapticPlaybackSupported. If changing
+        // that, the ringerUnderTest needs to be re-created.
+        when(mockSystemSettingsUtil.isAudioCoupledVibrationForRampingRingerEnabled())
+            .thenReturn(useHaptics);
+        when(mockRingtone.hasHapticChannels()).thenReturn(useHaptics);
+    }
+
+    private Ringtone ensureRingtoneMocked() {
+        Ringtone mockRingtone = mock(Ringtone.class);
+        when(mockRingtoneFactory.getRingtone(
+                any(Call.class), nullable(VolumeShaper.Configuration.class), anyBoolean()))
+                .thenReturn(mockRingtone);
+        when(mockRingtoneFactory.getHapticOnlyRingtone()).thenReturn(mockRingtone);
+        return mockRingtone;
+    }
 }
diff --git a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
index f6f2ae2..8bc1f2a 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
@@ -18,11 +18,13 @@
 
 import static android.Manifest.permission.CALL_PHONE;
 import static android.Manifest.permission.CALL_PRIVILEGED;
+import static android.Manifest.permission.MANAGE_OWN_CALLS;
 import static android.Manifest.permission.MODIFY_PHONE_STATE;
 import static android.Manifest.permission.READ_PHONE_NUMBERS;
 import static android.Manifest.permission.READ_PHONE_STATE;
 import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
 
+import android.Manifest;
 import android.app.ActivityManager;
 import android.app.AppOpsManager;
 import android.content.ComponentName;
@@ -31,13 +33,16 @@
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.graphics.drawable.Icon;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.OutcomeReceiver;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.telecom.CallAttributes;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
@@ -45,7 +50,9 @@
 import android.telephony.TelephonyManager;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.internal.telecom.ICallEventCallback;
 import com.android.internal.telecom.ITelecomService;
+import com.android.server.telecom.AnomalyReporterAdapter;
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallIntentProcessor;
 import com.android.server.telecom.CallState;
@@ -56,6 +63,9 @@
 import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.components.UserCallIntentProcessor;
 import com.android.server.telecom.components.UserCallIntentProcessorFactory;
+import com.android.server.telecom.voip.IncomingCallTransaction;
+import com.android.server.telecom.voip.OutgoingCallTransaction;
+import com.android.server.telecom.voip.TransactionManager;
 
 import org.junit.After;
 import org.junit.Before;
@@ -66,7 +76,6 @@
 import org.mockito.ArgumentMatcher;
 import org.mockito.Mock;
 
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import java.util.concurrent.Executor;
@@ -78,6 +87,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThrows;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.nullable;
@@ -94,13 +104,18 @@
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.isA;
 import static org.mockito.Mockito.when;
 
 @RunWith(JUnit4.class)
 public class TelecomServiceImplTest extends TelecomTestCase {
 
+    private static final String CALLING_PACKAGE = TelecomServiceImplTest.class.getPackageName();
+    private static final String TEST_NAME = "Alan Turing";
+    private static final Uri TEST_URI = Uri.fromParts("tel", "abc", "123");
     public static final String TEST_PACKAGE = "com.test";
     public static final String PACKAGE_NAME = "test";
 
@@ -174,6 +189,9 @@
     @Mock private UserCallIntentProcessor mUserCallIntentProcessor;
     private PackageManager mPackageManager;
     @Mock private ApplicationInfo mApplicationInfo;
+    @Mock private ICallEventCallback mICallEventCallback;
+    @Mock private TransactionManager mTransactionManager;
+    @Mock private AnomalyReporterAdapter mAnomalyReporterAdapter;
 
     private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
 
@@ -203,6 +221,8 @@
         doReturn(mContext).when(mContext).createContextAsUser(any(UserHandle.class), anyInt());
         doNothing().when(mContext).sendBroadcastAsUser(any(Intent.class), any(UserHandle.class),
                 anyString());
+        when(mContext.checkCallingOrSelfPermission(Manifest.permission.INTERACT_ACROSS_USERS))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
         doAnswer(invocation -> {
             mDefaultDialerObserver = invocation.getArgument(1);
             return null;
@@ -223,6 +243,8 @@
                 mSubscriptionManagerAdapter,
                 mSettingsSecureAdapter,
                 mLock);
+        telecomServiceImpl.setTransactionManager(mTransactionManager);
+        telecomServiceImpl.setAnomalyReporterAdapter(mAnomalyReporterAdapter);
         mTSIBinder = telecomServiceImpl.getBinder();
         mComponentContextFixture.setTelecomManager(mTelecomManager);
         when(mTelecomManager.getDefaultDialerPackage()).thenReturn(DEFAULT_DIALER_PACKAGE);
@@ -271,6 +293,51 @@
         assertEquals(SIP_PA_HANDLE_17, returnedHandleSip);
     }
 
+    /**
+     * Clear the groupId from the PhoneAccount if a package does NOT have MODIFY_PHONE_STATE
+     */
+    @SmallTest
+    @Test
+    public void testGroupIdIsClearedWhenPermissionIsMissing() throws RemoteException {
+        // GIVEN
+        PhoneAccount phoneAccount = makePhoneAccount(TEL_PA_HANDLE_CURRENT)
+                .setGroupId("testId")
+                .build();
+        // WHEN
+        doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccount(
+                eq(TEL_PA_HANDLE_CURRENT), any(UserHandle.class), anyBoolean());
+        doNothing().when(mAppOpsManager).checkPackage(anyInt(), anyString());
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingPermission(MODIFY_PHONE_STATE);
+        // THEN
+        PhoneAccount account =
+                mTSIBinder.getPhoneAccount(TEL_PA_HANDLE_CURRENT, PACKAGE_NAME);
+        assertEquals("***", account.getGroupId());
+    }
+
+    /**
+     * Ensure groupId is not cleared if a package has MODIFY_PHONE_STATE
+     */
+    @SmallTest
+    @Test
+    public void testGroupIdIsNotCleared() throws RemoteException {
+        // GIVEN
+        final String groupId = "testId";
+        PhoneAccount phoneAccount = makePhoneAccount(TEL_PA_HANDLE_CURRENT)
+                .setGroupId(groupId)
+                .build();
+        // WHEN
+        doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccount(
+                eq(TEL_PA_HANDLE_CURRENT), any(UserHandle.class), anyBoolean());
+        doNothing().when(mAppOpsManager).checkPackage(anyInt(), anyString());
+        doReturn(PackageManager.PERMISSION_GRANTED)
+                .when(mContext).checkCallingPermission(MODIFY_PHONE_STATE);
+        // THEN
+        PhoneAccount account =
+                mTSIBinder.getPhoneAccount(TEL_PA_HANDLE_CURRENT, DEFAULT_DIALER_PACKAGE);
+        assertEquals(groupId, account.getGroupId());
+    }
+
     @SmallTest
     @Test
     public void testGetDefaultOutgoingPhoneAccountSucceedsIfCallerIsSimCallManager()
@@ -354,19 +421,90 @@
                 .setUserSelectedOutgoingPhoneAccount(eq(TEL_PA_HANDLE_16), any(UserHandle.class));
     }
 
+    @Test
+    public void testAddCallWithOutgoingCall() throws RemoteException {
+        // GIVEN
+        CallAttributes mOutgoingCallAttributes = new CallAttributes.Builder(TEL_PA_HANDLE_CURRENT,
+                CallAttributes.DIRECTION_OUTGOING, TEST_NAME, TEST_URI)
+                .setCallType(CallAttributes.AUDIO_CALL)
+                .setCallCapabilities(CallAttributes.SUPPORTS_SET_INACTIVE)
+                .build();
+        PhoneAccount phoneAccount = makeMultiUserPhoneAccount(TEL_PA_HANDLE_CURRENT).build();
+        phoneAccount.setIsEnabled(true);
+
+        // WHEN
+        when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn(
+                phoneAccount);
+
+        doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccount(
+                eq(TEL_PA_HANDLE_CURRENT), any(UserHandle.class));
+
+        mTSIBinder.addCall(mOutgoingCallAttributes, mICallEventCallback, "1", CALLING_PACKAGE);
+
+        // THEN
+        verify(mTransactionManager, times(1))
+                .addTransaction(isA(OutgoingCallTransaction.class), isA(OutcomeReceiver.class));
+    }
+
+    @Test
+    public void testAddCallWithIncomingCall() throws RemoteException {
+        // GIVEN
+        CallAttributes mIncomingCallAttributes = new CallAttributes.Builder(TEL_PA_HANDLE_CURRENT,
+                CallAttributes.DIRECTION_INCOMING, TEST_NAME, TEST_URI)
+                .setCallType(CallAttributes.AUDIO_CALL)
+                .setCallCapabilities(CallAttributes.SUPPORTS_SET_INACTIVE)
+                .build();
+        PhoneAccount phoneAccount = makeMultiUserPhoneAccount(TEL_PA_HANDLE_CURRENT).build();
+        phoneAccount.setIsEnabled(true);
+
+        // WHEN
+        when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn(
+                phoneAccount);
+
+        doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccount(
+                eq(TEL_PA_HANDLE_CURRENT), any(UserHandle.class));
+
+        mTSIBinder.addCall(mIncomingCallAttributes, mICallEventCallback, "1", CALLING_PACKAGE);
+
+        // THEN
+        verify(mTransactionManager, times(1))
+                .addTransaction(isA(IncomingCallTransaction.class), isA(OutcomeReceiver.class));
+    }
+
+    @Test
+    public void testAddCallWithManagedPhoneAccount() throws RemoteException {
+        // GIVEN
+        CallAttributes attributes = new CallAttributes.Builder(TEL_PA_HANDLE_CURRENT,
+                CallAttributes.DIRECTION_OUTGOING, TEST_NAME, TEST_URI).build();
+        PhoneAccount phoneAccount = makeMultiUserPhoneAccount(TEL_PA_HANDLE_CURRENT)
+                .setCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
+                .build();
+        phoneAccount.setIsEnabled(true);
+
+        // WHEN
+        when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn(
+                phoneAccount);
+
+        doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccount(
+                eq(TEL_PA_HANDLE_CURRENT), any(UserHandle.class));
+
+        // THEN
+        try {
+            mTSIBinder.addCall(attributes, mICallEventCallback, "1", CALLING_PACKAGE);
+            fail("should have thrown IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // pass
+        }
+    }
+
     @SmallTest
     @Test
-    public void testSetUserSelectedOutgoingPhoneAccountFailure() throws RemoteException {
+    public void testSetUserSelectedOutgoingPhoneAccountWithoutPermission() throws RemoteException {
         doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
                 anyString(), nullable(String.class));
-        try {
-            mTSIBinder.setUserSelectedOutgoingPhoneAccount(TEL_PA_HANDLE_16);
-        } catch (SecurityException e) {
-            // desired result
-        }
-        verify(mFakePhoneAccountRegistrar, never())
-                .setUserSelectedOutgoingPhoneAccount(
-                        any(PhoneAccountHandle.class), any(UserHandle.class));
+
+        assertThrows(SecurityException.class,
+                () -> mTSIBinder.setUserSelectedOutgoingPhoneAccount(TEL_PA_HANDLE_16));
     }
 
     @SmallTest
@@ -378,11 +516,11 @@
         // Returns all phone accounts when getCallCapablePhoneAccounts is called.
         when(mFakePhoneAccountRegistrar
                 .getCallCapablePhoneAccounts(nullable(String.class), eq(true),
-                        nullable(UserHandle.class))).thenReturn(fullPHList);
+                        nullable(UserHandle.class), eq(true))).thenReturn(fullPHList);
         // Returns only enabled phone accounts when getCallCapablePhoneAccounts is called.
         when(mFakePhoneAccountRegistrar
                 .getCallCapablePhoneAccounts(nullable(String.class), eq(false),
-                        nullable(UserHandle.class))).thenReturn(smallPHList);
+                        nullable(UserHandle.class), eq(true))).thenReturn(smallPHList);
         makeAccountsVisibleToAllUsers(TEL_PA_HANDLE_16, SIP_PA_HANDLE_17);
 
         assertEquals(fullPHList,
@@ -395,21 +533,63 @@
 
     @SmallTest
     @Test
-    public void testGetCallCapablePhoneAccountsFailure() throws RemoteException {
+    public void testGetCallCapablePhoneAccountsWithoutPermission() throws RemoteException {
         List<String> enforcedPermissions = List.of(READ_PHONE_STATE, READ_PRIVILEGED_PHONE_STATE);
 
         doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
                 argThat(new AnyStringIn(enforcedPermissions)), anyString());
 
-        List<PhoneAccountHandle> result = null;
-        try {
-            result = mTSIBinder.getCallCapablePhoneAccounts(true, "", null).getList();
-        } catch (SecurityException e) {
-            // intended behavior
-        }
-        assertNull(result);
-        verify(mFakePhoneAccountRegistrar, never())
-                .getCallCapablePhoneAccounts(anyString(), anyBoolean(), any(UserHandle.class));
+        assertThrows(SecurityException.class,
+                () -> mTSIBinder.getCallCapablePhoneAccounts(true, "", null));
+    }
+
+    @SmallTest
+    @Test
+    public void testGetSelfManagedPhoneAccounts() throws RemoteException {
+        List<PhoneAccountHandle> accounts = List.of(TEL_PA_HANDLE_16);
+
+        when(mFakePhoneAccountRegistrar.getSelfManagedPhoneAccounts(nullable(UserHandle.class)))
+                .thenReturn(accounts);
+        makeAccountsVisibleToAllUsers(TEL_PA_HANDLE_16);
+
+        assertEquals(accounts,
+                mTSIBinder.getSelfManagedPhoneAccounts(DEFAULT_DIALER_PACKAGE, null).getList());
+    }
+
+    @SmallTest
+    @Test
+    public void testGetSelfManagedPhoneAccountsWithoutPermission() throws RemoteException {
+        List<String> enforcedPermissions = List.of(READ_PHONE_STATE, READ_PRIVILEGED_PHONE_STATE);
+        doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
+                argThat(new AnyStringIn(enforcedPermissions)), anyString());
+
+        assertThrows(SecurityException.class,
+                () -> mTSIBinder.getSelfManagedPhoneAccounts("", null));
+    }
+
+    @SmallTest
+    @Test
+    public void testGetOwnSelfManagedPhoneAccounts() throws RemoteException {
+        List<PhoneAccountHandle> accounts = List.of(TEL_PA_HANDLE_16);
+
+        when(mFakePhoneAccountRegistrar.getSelfManagedPhoneAccountsForPackage(
+                eq(DEFAULT_DIALER_PACKAGE), nullable(UserHandle.class)))
+                .thenReturn(accounts);
+        makeAccountsVisibleToAllUsers(TEL_PA_HANDLE_16);
+
+        assertEquals(accounts,
+                mTSIBinder.getOwnSelfManagedPhoneAccounts(DEFAULT_DIALER_PACKAGE, null).getList());
+    }
+
+    @SmallTest
+    @Test
+    public void testGetOwnSelfManagedPhoneAccountsWithoutPermission() throws RemoteException {
+        List<String> enforcedPermissions = List.of(MANAGE_OWN_CALLS);
+        doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
+                argThat(new AnyStringIn(enforcedPermissions)), anyString());
+
+        assertThrows(SecurityException.class,
+                () -> mTSIBinder.getOwnSelfManagedPhoneAccounts("", null));
     }
 
     @SmallTest
@@ -419,10 +599,12 @@
         List<PhoneAccountHandle> telPHList = List.of(TEL_PA_HANDLE_16);
 
         when(mFakePhoneAccountRegistrar
-                .getCallCapablePhoneAccounts(eq("tel"), anyBoolean(), any(UserHandle.class)))
+                .getCallCapablePhoneAccounts(eq("tel"), anyBoolean(),
+                        any(UserHandle.class), anyBoolean()))
                 .thenReturn(telPHList);
         when(mFakePhoneAccountRegistrar
-                .getCallCapablePhoneAccounts(eq("sip"), anyBoolean(), any(UserHandle.class)))
+                .getCallCapablePhoneAccounts(eq("sip"), anyBoolean(),
+                        any(UserHandle.class), anyBoolean()))
                 .thenReturn(sipPHList);
         makeAccountsVisibleToAllUsers(TEL_PA_HANDLE_16, SIP_PA_HANDLE_17);
 
@@ -436,11 +618,21 @@
 
     @SmallTest
     @Test
+    public void testGetPhoneAccountsSupportingSchemeWithoutPermission() throws RemoteException {
+        List<String> enforcedPermissions = List.of(MODIFY_PHONE_STATE);
+        doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
+                argThat(new AnyStringIn(enforcedPermissions)), anyString());
+
+        assertTrue(mTSIBinder.getPhoneAccountsSupportingScheme("any", "").getList().isEmpty());
+    }
+
+    @SmallTest
+    @Test
     public void testGetPhoneAccountsForPackage() throws RemoteException {
         List<PhoneAccountHandle> phoneAccountHandleList = List.of(
             TEL_PA_HANDLE_16, SIP_PA_HANDLE_17);
         when(mFakePhoneAccountRegistrar
-                .getPhoneAccountsForPackage(anyString(), any(UserHandle.class)))
+                .getAllPhoneAccountHandlesForPackage(any(UserHandle.class), anyString()))
                 .thenReturn(phoneAccountHandleList);
         makeAccountsVisibleToAllUsers(TEL_PA_HANDLE_16, SIP_PA_HANDLE_17);
         assertEquals(phoneAccountHandleList,
@@ -450,7 +642,20 @@
 
     @SmallTest
     @Test
+    public void testGetPhoneAccountsForPackageWithoutPermission() throws RemoteException {
+        List<String> enforcedPermissions = List.of(READ_PRIVILEGED_PHONE_STATE);
+        doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
+                argThat(new AnyStringIn(enforcedPermissions)), any());
+
+        assertThrows(SecurityException.class,
+                () -> mTSIBinder.getPhoneAccountsForPackage(""));
+    }
+
+    @SmallTest
+    @Test
     public void testGetPhoneAccount() throws Exception {
+        doReturn(PackageManager.PERMISSION_GRANTED)
+                .when(mContext).checkCallingPermission(MODIFY_PHONE_STATE);
         makeAccountsVisibleToAllUsers(TEL_PA_HANDLE_16, SIP_PA_HANDLE_17);
         assertEquals(TEL_PA_HANDLE_16, mTSIBinder.getPhoneAccount(TEL_PA_HANDLE_16,
                 mContext.getPackageName()).getAccountHandle());
@@ -466,15 +671,96 @@
 
     @SmallTest
     @Test
+    public void testGetAllPhoneAccountsCount() throws RemoteException {
+        List<PhoneAccount> phoneAccountList = List.of(
+                makePhoneAccount(TEL_PA_HANDLE_16).build(),
+                makePhoneAccount(SIP_PA_HANDLE_17).build());
+
+        when(mFakePhoneAccountRegistrar.getAllPhoneAccounts(any(UserHandle.class), anyBoolean()))
+                .thenReturn(phoneAccountList);
+
+        assertEquals(phoneAccountList.size(), mTSIBinder.getAllPhoneAccountsCount());
+    }
+
+    @SmallTest
+    @Test
+    public void testGetAllPhoneAccountsCountWithoutPermission() throws RemoteException {
+        List<String> enforcedPermissions = List.of(MODIFY_PHONE_STATE);
+        doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
+                argThat(new AnyStringIn(enforcedPermissions)), any());
+
+        assertThrows(SecurityException.class,
+                () -> mTSIBinder.getAllPhoneAccountsCount());
+    }
+
+    @SmallTest
+    @Test
     public void testGetAllPhoneAccounts() throws RemoteException {
         List<PhoneAccount> phoneAccountList = List.of(
                 makePhoneAccount(TEL_PA_HANDLE_16).build(),
                 makePhoneAccount(SIP_PA_HANDLE_17).build());
 
-        when(mFakePhoneAccountRegistrar.getAllPhoneAccounts(any(UserHandle.class)))
+        when(mFakePhoneAccountRegistrar.getAllPhoneAccounts(any(UserHandle.class), anyBoolean()))
                 .thenReturn(phoneAccountList);
 
-        assertEquals(2, mTSIBinder.getAllPhoneAccounts().getList().size());
+        assertEquals(phoneAccountList.size(), mTSIBinder.getAllPhoneAccounts().getList().size());
+    }
+
+    @SmallTest
+    @Test
+    public void testGetAllPhoneAccountsWithoutPermission() throws RemoteException {
+        List<String> enforcedPermissions = List.of(MODIFY_PHONE_STATE);
+        doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
+                argThat(new AnyStringIn(enforcedPermissions)), any());
+
+        assertThrows(SecurityException.class,
+                () -> mTSIBinder.getAllPhoneAccounts());
+    }
+
+    @SmallTest
+    @Test
+    public void testGetAllPhoneAccountHandles() throws RemoteException {
+        List<PhoneAccountHandle> handles = List.of(TEL_PA_HANDLE_16, SIP_PA_HANDLE_17);
+        when(mFakePhoneAccountRegistrar.getAllPhoneAccountHandles(
+                any(UserHandle.class), anyBoolean())).thenReturn(handles);
+
+        assertEquals(handles, mTSIBinder.getAllPhoneAccountHandles().getList());
+    }
+
+    @SmallTest
+    @Test
+    public void testGetAllPhoneAccountHandlesWithoutPermission() throws RemoteException {
+        List<String> enforcedPermissions = List.of(MODIFY_PHONE_STATE);
+        doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
+                argThat(new AnyStringIn(enforcedPermissions)), any());
+
+        assertThrows(SecurityException.class,
+                () -> mTSIBinder.getAllPhoneAccountHandles());
+    }
+
+    @SmallTest
+    @Test
+    public void testGetSimCallManager() throws RemoteException {
+        final PhoneAccountHandle handle = TEL_PA_HANDLE_16;
+        final int subId = 1;
+        when(mFakePhoneAccountRegistrar.getSimCallManager(eq(subId), any(UserHandle.class)))
+                .thenReturn(handle);
+
+        assertEquals(handle, mTSIBinder.getSimCallManager(subId, "any"));
+    }
+
+    @SmallTest
+    @Test
+    public void testGetSimCallManagerForUser() throws RemoteException {
+        final PhoneAccountHandle handle = TEL_PA_HANDLE_16;
+        final int user = 1;
+        when(mFakePhoneAccountRegistrar.getSimCallManager(
+                argThat(userHandle -> {
+                    return userHandle.getIdentifier() == user;
+                })))
+                .thenReturn(handle);
+
+        assertEquals(handle, mTSIBinder.getSimCallManagerForUser(user, "any"));
     }
 
     @SmallTest
@@ -492,6 +778,65 @@
 
     @SmallTest
     @Test
+    public void testRegisterPhoneAccountWithoutPermissionAnomalyReported() throws RemoteException {
+        PhoneAccountHandle handle = new PhoneAccountHandle(
+                new ComponentName("package", "cs"), "test", Binder.getCallingUserHandle());
+        PhoneAccount account = makeSelfManagedPhoneAccount(handle).build();
+
+        List<String> enforcedPermissions = List.of(MANAGE_OWN_CALLS);
+        doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
+                argThat(new AnyStringIn(enforcedPermissions)), any());
+
+        registerPhoneAccountTestHelper(account, false);
+        verify(mAnomalyReporterAdapter).reportAnomaly(
+                TelecomServiceImpl.REGISTER_PHONE_ACCOUNT_ERROR_UUID,
+                TelecomServiceImpl.REGISTER_PHONE_ACCOUNT_ERROR_MSG);
+    }
+
+    @SmallTest
+    @Test
+    public void testRegisterPhoneAccountSelfManagedWithoutPermission() throws RemoteException {
+        PhoneAccountHandle handle = new PhoneAccountHandle(
+                new ComponentName("package", "cs"), "test", Binder.getCallingUserHandle());
+        PhoneAccount account = makeSelfManagedPhoneAccount(handle).build();
+
+        List<String> enforcedPermissions = List.of(MANAGE_OWN_CALLS);
+        doThrow(new SecurityException()).when(mContext).enforceCallingOrSelfPermission(
+                argThat(new AnyStringIn(enforcedPermissions)), any());
+
+        registerPhoneAccountTestHelper(account, false);
+    }
+
+    @SmallTest
+    @Test
+    public void testRegisterPhoneAccountSelfManagedInvalidCapabilities() throws RemoteException {
+        PhoneAccountHandle handle = new PhoneAccountHandle(
+                new ComponentName("package", "cs"), "test", Binder.getCallingUserHandle());
+
+        PhoneAccount selfManagedCallProviderAccount = makePhoneAccount(handle)
+                .setCapabilities(
+                    PhoneAccount.CAPABILITY_SELF_MANAGED |
+                    PhoneAccount.CAPABILITY_CALL_PROVIDER)
+                .build();
+        registerPhoneAccountTestHelper(selfManagedCallProviderAccount, false);
+
+        PhoneAccount selfManagedConnectionManagerAccount = makePhoneAccount(handle)
+                .setCapabilities(
+                    PhoneAccount.CAPABILITY_SELF_MANAGED |
+                    PhoneAccount.CAPABILITY_CONNECTION_MANAGER)
+                .build();
+        registerPhoneAccountTestHelper(selfManagedConnectionManagerAccount, false);
+
+        PhoneAccount selfManagedSimSubscriptionAccount = makePhoneAccount(handle)
+                .setCapabilities(
+                    PhoneAccount.CAPABILITY_SELF_MANAGED |
+                    PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)
+                .build();
+        registerPhoneAccountTestHelper(selfManagedSimSubscriptionAccount, false);
+    }
+
+    @SmallTest
+    @Test
     public void testRegisterPhoneAccountWithoutModifyPermission() throws RemoteException {
         // tests the case where the package does not have MODIFY_PHONE_STATE but is
         // registering its own phone account as a third-party connection service
@@ -589,7 +934,7 @@
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
         boolean didExceptionOccur = false;
         try {
-            mTSIBinder.registerPhoneAccount(testPhoneAccount);
+            mTSIBinder.registerPhoneAccount(testPhoneAccount, CALLING_PACKAGE);
         } catch (Exception e) {
             didExceptionOccur = true;
         }
@@ -606,6 +951,26 @@
 
     @SmallTest
     @Test
+    public void testRegisterPhoneAccountImageIconCrossUser() throws RemoteException {
+        String packageNameToUse = "com.android.officialpackage";
+        PhoneAccountHandle phHandle = new PhoneAccountHandle(new ComponentName(
+                packageNameToUse, "cs"), "test", Binder.getCallingUserHandle());
+        Icon icon = Icon.createWithContentUri("content://10@media/external/images/media/");
+        PhoneAccount phoneAccount = makePhoneAccount(phHandle).setIcon(icon).build();
+        doReturn(PackageManager.PERMISSION_GRANTED)
+                .when(mContext).checkCallingOrSelfPermission(MODIFY_PHONE_STATE);
+
+        // This should fail; security exception will be thrown.
+        registerPhoneAccountTestHelper(phoneAccount, false);
+
+        icon = Icon.createWithContentUri("content://0@media/external/images/media/");
+        phoneAccount = makePhoneAccount(phHandle).setIcon(icon).build();
+        // This should succeed.
+        registerPhoneAccountTestHelper(phoneAccount, true);
+    }
+
+    @SmallTest
+    @Test
     public void testUnregisterPhoneAccount() throws RemoteException {
         String packageNameToUse = "com.android.officialpackage";
         PhoneAccountHandle phHandle = new PhoneAccountHandle(new ComponentName(
@@ -615,7 +980,7 @@
         doReturn(PackageManager.PERMISSION_GRANTED)
                 .when(mContext).checkCallingOrSelfPermission(MODIFY_PHONE_STATE);
 
-        mTSIBinder.unregisterPhoneAccount(phHandle);
+        mTSIBinder.unregisterPhoneAccount(phHandle, CALLING_PACKAGE);
         verify(mFakePhoneAccountRegistrar).unregisterPhoneAccount(phHandle);
     }
 
@@ -632,7 +997,7 @@
         when(pm.hasSystemFeature(PackageManager.FEATURE_TELECOM)).thenReturn(false);
 
         try {
-            mTSIBinder.unregisterPhoneAccount(phHandle);
+            mTSIBinder.unregisterPhoneAccount(phHandle, CALLING_PACKAGE);
         } catch (UnsupportedOperationException e) {
             // expected behavior
         }
@@ -644,25 +1009,47 @@
 
     @SmallTest
     @Test
+    public void testClearAccounts() throws RemoteException {
+        mTSIBinder.clearAccounts(CALLING_PACKAGE);
+
+        verify(mFakePhoneAccountRegistrar)
+                .clearAccounts(CALLING_PACKAGE, mTSIBinder.getCallingUserHandle());
+    }
+
+    @SmallTest
+    @Test
+    public void testClearAccountsWithoutPermission() throws RemoteException {
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingOrSelfPermission(MODIFY_PHONE_STATE);
+
+        assertThrows(UnsupportedOperationException.class,
+                () -> mTSIBinder.clearAccounts(CALLING_PACKAGE));
+    }
+
+    @SmallTest
+    @Test
     public void testAddNewIncomingCall() throws Exception {
-        PhoneAccount phoneAccount = makePhoneAccount(TEL_PA_HANDLE_CURRENT).build();
+        PhoneAccount phoneAccount = makePhoneAccount(TEL_PA_HANDLE_16).build();
         phoneAccount.setIsEnabled(true);
         doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccount(
-                eq(TEL_PA_HANDLE_CURRENT), any(UserHandle.class));
+                eq(TEL_PA_HANDLE_16), any(UserHandle.class));
         doNothing().when(mAppOpsManager).checkPackage(anyInt(), anyString());
         Bundle extras = createSampleExtras();
 
-        mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_CURRENT, extras);
+        mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_16, extras, CALLING_PACKAGE);
 
+        verify(mFakePhoneAccountRegistrar).getPhoneAccount(
+                TEL_PA_HANDLE_16, TEL_PA_HANDLE_16.getUserHandle());
         addCallTestHelper(TelecomManager.ACTION_INCOMING_CALL,
-                CallIntentProcessor.KEY_IS_INCOMING_CALL, extras, false);
+                CallIntentProcessor.KEY_IS_INCOMING_CALL, extras,
+                TEL_PA_HANDLE_16, false);
     }
 
     @SmallTest
     @Test
     public void testAddNewIncomingCallFailure() throws Exception {
         try {
-            mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_16, null);
+            mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_16, null, CALLING_PACKAGE);
         } catch (SecurityException e) {
             // expected
         }
@@ -670,7 +1057,7 @@
         doThrow(new SecurityException()).when(mAppOpsManager).checkPackage(anyInt(), anyString());
 
         try {
-            mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_CURRENT, null);
+            mTSIBinder.addNewIncomingCall(TEL_PA_HANDLE_CURRENT, null, CALLING_PACKAGE);
         } catch (SecurityException e) {
             // expected
         }
@@ -693,7 +1080,7 @@
         mTSIBinder.addNewUnknownCall(TEL_PA_HANDLE_CURRENT, extras);
 
         addCallTestHelper(TelecomManager.ACTION_NEW_UNKNOWN_CALL,
-                CallIntentProcessor.KEY_IS_UNKNOWN_CALL, extras, true);
+                CallIntentProcessor.KEY_IS_UNKNOWN_CALL, extras, TEL_PA_HANDLE_CURRENT, true);
     }
 
     @SmallTest
@@ -719,7 +1106,8 @@
     }
 
     private void addCallTestHelper(String expectedAction, String extraCallKey,
-            Bundle expectedExtras, boolean isUnknown) {
+            Bundle expectedExtras, PhoneAccountHandle expectedPhoneAccountHandle,
+            boolean isUnknown) {
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
         if (isUnknown) {
             verify(mCallIntentProcessorAdapter).processUnknownCallIntent(any(CallsManager.class),
@@ -731,7 +1119,7 @@
         Intent capturedIntent = intentCaptor.getValue();
         assertEquals(expectedAction, capturedIntent.getAction());
         Bundle intentExtras = capturedIntent.getExtras();
-        assertEquals(TEL_PA_HANDLE_CURRENT,
+        assertEquals(expectedPhoneAccountHandle,
                 intentExtras.get(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE));
         assertTrue(intentExtras.getBoolean(extraCallKey));
 
@@ -747,88 +1135,517 @@
         }
     }
 
+    /**
+     * 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);
         doReturn(PackageManager.PERMISSION_GRANTED)
-                .when(mContext).checkCallingPermission(CALL_PHONE);
+                .when(mContext).checkCallingOrSelfPermission(CALL_PHONE);
         doReturn(PackageManager.PERMISSION_DENIED)
-                .when(mContext).checkCallingPermission(CALL_PRIVILEGED);
+                .when(mContext).checkCallingOrSelfPermission(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).checkCallingOrSelfPermission(CALL_PHONE);
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingOrSelfPermission(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());
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingOrSelfPermission(CALL_PRIVILEGED);
+        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());
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingOrSelfPermission(CALL_PRIVILEGED);
+        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).checkCallingOrSelfPermission(CALL_PHONE);
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingOrSelfPermission(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());
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingOrSelfPermission(CALL_PRIVILEGED);
+        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());
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingOrSelfPermission(CALL_PRIVILEGED);
+        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).checkCallingOrSelfPermission(CALL_PHONE);
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingOrSelfPermission(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).checkCallingOrSelfPermission(CALL_PHONE);
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingOrSelfPermission(CALL_PRIVILEGED);
+
+        mTSIBinder.placeCall(handle, extras, DEFAULT_DIALER_PACKAGE, null);
+        placeCallTestHelper(handle, extras, /*isSelfManagedExpected*/ false,
+                /*shouldNonEmergencyBeAllowed*/ true);
+    }
+
+    /**
+     * In the case that there is a managed normal call request and the app has CALL_PRIVILEGED
+     * permission, place call should complete successfully.
+     */
+    @SmallTest
+    @Test
+    public void testPlaceCallPrivileged() throws Exception {
+        doReturn(false).when(mDefaultDialerCache).isDefaultOrSystemDialer(
+                eq(DEFAULT_DIALER_PACKAGE), anyInt());
+        Uri handle = Uri.parse("tel:6505551234");
+
+        // CALL_PHONE is not granted, but CALL_PRIVILEGED is
+        doThrow(new SecurityException())
+                .when(mContext).enforceCallingOrSelfPermission(
+                        eq(Manifest.permission.MANAGE_OWN_CALLS), anyString());
+        doReturn(PackageManager.PERMISSION_GRANTED)
+                .when(mContext).checkCallingOrSelfPermission(CALL_PRIVILEGED);
+        doThrow(new SecurityException()).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, null, PACKAGE_NAME + "2", null);
+        } catch(SecurityException e) {
+            fail("Expected no SecurityException - CALL_PRIVILEGED is granted");
+        }
+        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mUserCallIntentProcessor).processIntent(intentCaptor.capture(), anyString(),
+                eq(false), eq(true), eq(true));
+        Intent capturedIntent = intentCaptor.getValue();
+        assertEquals(Intent.ACTION_CALL_PRIVILEGED, capturedIntent.getAction());
+        assertEquals(handle, capturedIntent.getData());
+    }
+
+    /**
+     * 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);
         doReturn(PackageManager.PERMISSION_GRANTED)
-                .when(mContext).checkCallingPermission(CALL_PHONE);
+                .when(mContext).checkCallingOrSelfPermission(CALL_PHONE);
         doReturn(PackageManager.PERMISSION_DENIED)
-                .when(mContext).checkCallingPermission(CALL_PRIVILEGED);
+                .when(mContext).checkCallingOrSelfPermission(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);
         doReturn(PackageManager.PERMISSION_DENIED)
-                .when(mContext).checkCallingPermission(CALL_PHONE);
+                .when(mContext).checkCallingOrSelfPermission(CALL_PHONE);
         doReturn(PackageManager.PERMISSION_DENIED)
-                .when(mContext).checkCallingPermission(CALL_PRIVILEGED);
+                .when(mContext).checkCallingOrSelfPermission(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());
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingOrSelfPermission(CALL_PRIVILEGED);
 
         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());
+        doReturn(PackageManager.PERMISSION_DENIED)
+                .when(mContext).checkCallingOrSelfPermission(CALL_PRIVILEGED);
+        when(mAppOpsManager.noteOp(eq(AppOpsManager.OP_CALL_PHONE), anyInt(), anyString(),
+                nullable(String.class), nullable(String.class)))
+                .thenReturn(AppOpsManager.MODE_IGNORED);
+
+        try {
+            mTSIBinder.placeCall(handle, extras, "arbitrary_package_name", null);
+            fail("Expected SecurityException because CALL_PHONE was not granted to caller");
+        } catch (SecurityException e) {
+            // expected
+        }
+
+        verify(mUserCallIntentProcessor, never())
+                .processIntent(any(Intent.class), anyString(), eq(false), anyBoolean(), eq(true));
     }
 
     @SmallTest
@@ -1090,6 +1907,29 @@
 
     @SmallTest
     @Test
+    public void testGetDefaultDialerPackageForUser() throws Exception {
+        final int userId = 1;
+        final String packageName = "some.package";
+
+        when(mDefaultDialerCache.getDefaultDialerApplication(userId))
+                .thenReturn(packageName);
+
+        assertEquals(packageName, mTSIBinder.getDefaultDialerPackageForUser(userId));
+    }
+
+    @SmallTest
+    @Test
+    public void testGetSystemDialerPackage() throws Exception {
+        final String packageName = "some.package";
+
+        when(mDefaultDialerCache.getSystemDialerApplication())
+                .thenReturn(packageName);
+
+        assertEquals(packageName, mTSIBinder.getSystemDialerPackage(CALLING_PACKAGE));
+    }
+
+    @SmallTest
+    @Test
     public void testEndCallWithRingingForegroundCall() throws Exception {
         Call call = mock(Call.class);
         when(call.getState()).thenReturn(CallState.RINGING);
@@ -1123,8 +1963,7 @@
     public void testEndCallWithNoForegroundCall() throws Exception {
         Call call = mock(Call.class);
         when(call.getState()).thenReturn(CallState.ACTIVE);
-        when(mFakeCallsManager.getFirstCallWithState(any()))
-                .thenReturn(call);
+        when(mFakeCallsManager.getFirstCallWithState(any())).thenReturn(call);
         assertTrue(mTSIBinder.endCall(TEST_PACKAGE));
         verify(mFakeCallsManager).disconnectCall(eq(call));
     }
@@ -1165,14 +2004,16 @@
     @SmallTest
     @Test
     public void testIsInCall() throws Exception {
-        when(mFakeCallsManager.hasOngoingCalls()).thenReturn(true);
+        when(mFakeCallsManager.hasOngoingCalls(any(UserHandle.class), anyBoolean()))
+                .thenReturn(true);
         assertTrue(mTSIBinder.isInCall(DEFAULT_DIALER_PACKAGE, null));
     }
 
     @SmallTest
     @Test
     public void testNotIsInCall() throws Exception {
-        when(mFakeCallsManager.hasOngoingCalls()).thenReturn(false);
+        when(mFakeCallsManager.hasOngoingCalls(any(UserHandle.class), anyBoolean()))
+                .thenReturn(false);
         assertFalse(mTSIBinder.isInCall(DEFAULT_DIALER_PACKAGE, null));
     }
 
@@ -1187,20 +2028,22 @@
         } catch (SecurityException e) {
             // desired result
         }
-        verify(mFakeCallsManager, never()).hasOngoingCalls();
+        verify(mFakeCallsManager, never()).hasOngoingCalls(any(UserHandle.class), anyBoolean());
     }
 
     @SmallTest
     @Test
     public void testIsInManagedCall() throws Exception {
-        when(mFakeCallsManager.hasOngoingManagedCalls()).thenReturn(true);
+        when(mFakeCallsManager.hasOngoingManagedCalls(any(UserHandle.class), anyBoolean()))
+                .thenReturn(true);
         assertTrue(mTSIBinder.isInManagedCall(DEFAULT_DIALER_PACKAGE, null));
     }
 
     @SmallTest
     @Test
     public void testNotIsInManagedCall() throws Exception {
-        when(mFakeCallsManager.hasOngoingManagedCalls()).thenReturn(false);
+        when(mFakeCallsManager.hasOngoingManagedCalls(any(UserHandle.class), anyBoolean()))
+                .thenReturn(false);
         assertFalse(mTSIBinder.isInManagedCall(DEFAULT_DIALER_PACKAGE, null));
     }
 
@@ -1215,7 +2058,7 @@
         } catch (SecurityException e) {
             // desired result
         }
-        verify(mFakeCallsManager, never()).hasOngoingCalls();
+        verify(mFakeCallsManager, never()).hasOngoingCalls(any(UserHandle.class), anyBoolean());
     }
 
     /**
@@ -1251,6 +2094,22 @@
         verify(mFakeCallsManager, never()).answerCall(eq(call), anyInt());
     }
 
+    @SmallTest
+    @Test
+    public void testGetAdnUriForPhoneAccount() throws Exception {
+        final int subId = 1;
+        final Uri adnUri = Uri.parse("content://icc/adn/subId/" + subId);
+        PhoneAccount phoneAccount = makePhoneAccount(TEL_PA_HANDLE_CURRENT).build();
+        when(mFakePhoneAccountRegistrar.getPhoneAccount(
+                eq(TEL_PA_HANDLE_CURRENT), any(UserHandle.class)))
+                .thenReturn(phoneAccount);
+        when(mFakePhoneAccountRegistrar.getSubscriptionIdForPhoneAccount(TEL_PA_HANDLE_CURRENT))
+                .thenReturn(subId);
+
+        assertEquals(adnUri,
+                mTSIBinder.getAdnUriForPhoneAccount(TEL_PA_HANDLE_CURRENT, DEFAULT_DIALER_PACKAGE));
+    }
+
     /**
      * Register phone accounts for the supplied PhoneAccountHandles to make them
      * visible to all users (via the isVisibleToCaller method in TelecomServiceImpl.
@@ -1275,6 +2134,12 @@
         return paBuilder;
     }
 
+    private PhoneAccount.Builder makeSelfManagedPhoneAccount(PhoneAccountHandle paHandle) {
+        PhoneAccount.Builder paBuilder = makePhoneAccount(paHandle);
+        paBuilder.setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED);
+        return paBuilder;
+    }
+
     private PhoneAccount.Builder makePhoneAccount(PhoneAccountHandle paHandle) {
         return new PhoneAccount.Builder(paHandle, "testLabel");
     }
@@ -1286,6 +2151,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..b962b2a 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.
@@ -411,12 +428,13 @@
         super.tearDown();
     }
 
-    protected ParcelableCall makeConferenceCall() throws Exception {
-        IdPair callId1 = startAndMakeActiveOutgoingCall("650-555-1212",
-                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
+    protected ParcelableCall makeConferenceCall(
+            Intent callIntentExtras1, Intent callIntentExtras2) throws Exception {
+        IdPair callId1 = startAndMakeActiveOutgoingCallWithExtras("650-555-1212",
+                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA, callIntentExtras1);
 
-        IdPair callId2 = startAndMakeActiveOutgoingCall("650-555-1213",
-                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
+        IdPair callId2 = startAndMakeActiveOutgoingCallWithExtras("650-555-1213",
+                mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA, callIntentExtras2);
 
         IInCallAdapter inCallAdapter = mInCallServiceFixtureX.getInCallAdapter();
         inCallAdapter.conference(callId1.mCallId, callId2.mCallId);
@@ -473,7 +491,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 +517,8 @@
                             WiredHeadsetManager wiredHeadsetManager,
                             StatusBarNotifier statusBarNotifier,
                             CallAudioManager.AudioServiceFactory audioServiceFactory,
-                            int earpieceControl) {
+                            int earpieceControl,
+                            Executor asyncTaskExecutor) {
                         return new CallAudioRouteStateMachine(context,
                                 callsManager,
                                 bluetoothManager,
@@ -507,7 +527,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 +547,9 @@
                             ContactsAsyncHelper.ContentResolverAdapter adapter) {
                         return new ContactsAsyncHelper(adapter, mHandlerThread.getLooper());
                     }
-                }, mDeviceIdleControllerAdapter);
+                }, mDeviceIdleControllerAdapter, mAccessibilityManagerAdapter,
+                Runnable::run,
+                mBlockedNumbersAdapter);
 
         mComponentContextFixture.setTelecomManager(new TelecomManager(
                 mComponentContextFixture.getTestDouble(),
@@ -618,7 +641,7 @@
 
         startOutgoingPhoneCallWaitForBroadcaster(number, null,
                 connectionServiceFixture, Process.myUserHandle(), VideoProfile.STATE_AUDIO_ONLY,
-                false /*isEmergency*/);
+                false /*isEmergency*/, null);
 
         return mInCallServiceFixtureX.mLatestCallId;
     }
@@ -648,17 +671,17 @@
             throws Exception {
 
         return startOutgoingPhoneCall(number, phoneAccountHandle, connectionServiceFixture,
-                initiatingUser, VideoProfile.STATE_AUDIO_ONLY);
+                initiatingUser, VideoProfile.STATE_AUDIO_ONLY, null);
     }
 
     protected IdPair startOutgoingPhoneCall(String number, PhoneAccountHandle phoneAccountHandle,
             ConnectionServiceFixture connectionServiceFixture, UserHandle initiatingUser,
-            int videoState) throws Exception {
+            int videoState, Intent callIntentExtras) throws Exception {
         int startingNumConnections = connectionServiceFixture.mConnectionById.size();
         int startingNumCalls = mInCallServiceFixtureX.mCallById.size();
 
         startOutgoingPhoneCallPendingCreateConnection(number, phoneAccountHandle,
-                connectionServiceFixture, initiatingUser, videoState);
+                connectionServiceFixture, initiatingUser, videoState, callIntentExtras);
 
         verify(connectionServiceFixture.getTestDouble(), timeout(TEST_TIMEOUT))
                 .createConnectionComplete(anyString(), any());
@@ -701,7 +724,7 @@
 
         // Call will not use the ordered broadcaster, since it is an Emergency Call
         startOutgoingPhoneCallWaitForBroadcaster(number, phoneAccountHandle,
-                connectionServiceFixture, initiatingUser, videoState, true /*isEmergency*/);
+                connectionServiceFixture, initiatingUser, videoState, true /*isEmergency*/, null);
 
         return outgoingCallCreateConnectionComplete(startingNumConnections, startingNumCalls,
                 phoneAccountHandle, connectionServiceFixture);
@@ -710,7 +733,7 @@
     protected void startOutgoingPhoneCallWaitForBroadcaster(String number,
             PhoneAccountHandle phoneAccountHandle,
             ConnectionServiceFixture connectionServiceFixture, UserHandle initiatingUser,
-            int videoState, boolean isEmergency) throws Exception {
+            int videoState, boolean isEmergency, Intent actionCallIntent) throws Exception {
         reset(connectionServiceFixture.getTestDouble(), mInCallServiceFixtureX.getTestDouble(),
                 mInCallServiceFixtureY.getTestDouble());
 
@@ -723,7 +746,9 @@
 
         boolean hasInCallAdapter = mInCallServiceFixtureX.mInCallAdapter != null;
 
-        Intent actionCallIntent = new Intent();
+        if (actionCallIntent == null) {
+            actionCallIntent = new Intent();
+        }
         actionCallIntent.setData(Uri.parse("tel:" + number));
         actionCallIntent.putExtra(Intent.EXTRA_PHONE_NUMBER, number);
         if(isEmergency) {
@@ -743,7 +768,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.
@@ -768,9 +793,10 @@
     protected String startOutgoingPhoneCallPendingCreateConnection(String number,
             PhoneAccountHandle phoneAccountHandle,
             ConnectionServiceFixture connectionServiceFixture, UserHandle initiatingUser,
-            int videoState) throws Exception {
+            int videoState, Intent callIntentExtras) throws Exception {
         startOutgoingPhoneCallWaitForBroadcaster(number,phoneAccountHandle,
-                connectionServiceFixture, initiatingUser, videoState, false /*isEmergency*/);
+                connectionServiceFixture, initiatingUser,
+                videoState, false /*isEmergency*/, callIntentExtras);
         waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
 
         verifyAndProcessOutgoingCallBroadcast(phoneAccountHandle);
@@ -875,14 +901,24 @@
             PhoneAccountHandle phoneAccountHandle,
             final ConnectionServiceFixture connectionServiceFixture) throws Exception {
         return startIncomingPhoneCall(number, phoneAccountHandle, VideoProfile.STATE_AUDIO_ONLY,
-                connectionServiceFixture);
+                connectionServiceFixture, null);
+    }
+
+    protected IdPair startIncomingPhoneCallWithExtras(
+            String number,
+            PhoneAccountHandle phoneAccountHandle,
+            final ConnectionServiceFixture connectionServiceFixture,
+            Bundle extras) throws Exception {
+        return startIncomingPhoneCall(number, phoneAccountHandle, VideoProfile.STATE_AUDIO_ONLY,
+                connectionServiceFixture, extras);
     }
 
     protected IdPair startIncomingPhoneCall(
             String number,
             PhoneAccountHandle phoneAccountHandle,
             int videoState,
-            final ConnectionServiceFixture connectionServiceFixture) throws Exception {
+            final ConnectionServiceFixture connectionServiceFixture,
+            Bundle extras) throws Exception {
         reset(connectionServiceFixture.getTestDouble(), mInCallServiceFixtureX.getTestDouble(),
                 mInCallServiceFixtureY.getTestDouble());
 
@@ -899,12 +935,14 @@
                 new IncomingCallAddedListener(incomingCallAddedLatch);
         mTelecomSystem.getCallsManager().addListener(callAddedListener);
 
-        Bundle extras = new Bundle();
+        if (extras == null) {
+            extras = new Bundle();
+        }
         extras.putParcelable(
                 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(),
@@ -990,7 +1028,16 @@
             PhoneAccountHandle phoneAccountHandle,
             ConnectionServiceFixture connectionServiceFixture) throws Exception {
         return startAndMakeActiveOutgoingCall(number, phoneAccountHandle, connectionServiceFixture,
-                VideoProfile.STATE_AUDIO_ONLY);
+                VideoProfile.STATE_AUDIO_ONLY, null);
+    }
+
+    protected IdPair startAndMakeActiveOutgoingCallWithExtras(
+            String number,
+            PhoneAccountHandle phoneAccountHandle,
+            ConnectionServiceFixture connectionServiceFixture,
+            Intent callIntentExtras) throws Exception {
+        return startAndMakeActiveOutgoingCall(number, phoneAccountHandle, connectionServiceFixture,
+                VideoProfile.STATE_AUDIO_ONLY, callIntentExtras);
     }
 
     // A simple outgoing call, verifying that the appropriate connection service is contacted,
@@ -998,9 +1045,10 @@
     protected IdPair startAndMakeActiveOutgoingCall(
             String number,
             PhoneAccountHandle phoneAccountHandle,
-            ConnectionServiceFixture connectionServiceFixture, int videoState) throws Exception {
+            ConnectionServiceFixture connectionServiceFixture, int videoState,
+            Intent callIntentExtras) throws Exception {
         IdPair ids = startOutgoingPhoneCall(number, phoneAccountHandle, connectionServiceFixture,
-                Process.myUserHandle(), videoState);
+                Process.myUserHandle(), videoState, callIntentExtras);
 
         connectionServiceFixture.sendSetDialing(ids.mConnectionId);
         if (phoneAccountHandle != mPhoneAccountSelfManaged.getAccountHandle()) {
@@ -1109,7 +1157,7 @@
             PhoneAccountHandle phoneAccountHandle,
             ConnectionServiceFixture connectionServiceFixture) throws Exception {
         IdPair ids = startOutgoingPhoneCall(number, phoneAccountHandle, connectionServiceFixture,
-                Process.myUserHandle(), VideoProfile.STATE_AUDIO_ONLY);
+                Process.myUserHandle(), VideoProfile.STATE_AUDIO_ONLY, null);
 
         connectionServiceFixture.sendSetDialing(ids.mConnectionId);
         if (phoneAccountHandle != mPhoneAccountSelfManaged.getAccountHandle()) {
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..fa5f2a2
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/TransactionalServiceWrapperTest.java
@@ -0,0 +1,182 @@
+/*
+ * 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.IBinder;
+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;
+    @Mock private IBinder mIBinder;
+    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);
+        Mockito.when(mCallEventCallback.asBinder()).thenReturn(mIBinder);
+        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/VideoCallTests.java b/tests/src/com/android/server/telecom/tests/VideoCallTests.java
index 97e71d1..84beedc 100644
--- a/tests/src/com/android/server/telecom/tests/VideoCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/VideoCallTests.java
@@ -105,7 +105,7 @@
         // Start an incoming video call.
         IdPair ids = startAndMakeActiveOutgoingCall("650-555-1212",
                 mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA,
-                VideoProfile.STATE_BIDIRECTIONAL);
+                VideoProfile.STATE_BIDIRECTIONAL, null);
 
         verifyAudioRoute(CallAudioState.ROUTE_SPEAKER);
     }
@@ -121,7 +121,7 @@
         // Start an incoming video call.
         IdPair ids = startAndMakeActiveOutgoingCall("650-555-1212",
                 mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA,
-                VideoProfile.STATE_TX_ENABLED);
+                VideoProfile.STATE_TX_ENABLED, null);
 
         verifyAudioRoute(CallAudioState.ROUTE_SPEAKER);
     }
@@ -137,7 +137,7 @@
         // Start an incoming video call.
         IdPair ids = startAndMakeActiveOutgoingCall("650-555-1212",
                 mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA,
-                VideoProfile.STATE_AUDIO_ONLY);
+                VideoProfile.STATE_AUDIO_ONLY, null);
 
         verifyAudioRoute(CallAudioState.ROUTE_EARPIECE);
     }
@@ -165,7 +165,7 @@
     @Test
     public void testIncomingVideoCallMissedCheckVideoHistory() throws Exception {
         IdPair ids = startIncomingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
-                VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA);
+                VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA, null);
         com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
                 .iterator().next();
 
@@ -182,7 +182,7 @@
     @Test
     public void testIncomingVideoCallRejectedCheckVideoHistory() throws Exception {
         IdPair ids = startIncomingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
-                VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA);
+                VideoProfile.STATE_BIDIRECTIONAL, mConnectionServiceFixtureA, null);
         com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
                 .iterator().next();
 
@@ -201,7 +201,7 @@
     public void testOutgoingVideoCallCanceledCheckVideoHistory() throws Exception {
         IdPair ids = startOutgoingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
                 mConnectionServiceFixtureA, Process.myUserHandle(),
-                VideoProfile.STATE_BIDIRECTIONAL);
+                VideoProfile.STATE_BIDIRECTIONAL, null);
         com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
                 .iterator().next();
 
@@ -219,7 +219,7 @@
     public void testOutgoingVideoCallRejectedCheckVideoHistory() throws Exception {
         IdPair ids = startOutgoingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
                 mConnectionServiceFixtureA, Process.myUserHandle(),
-                VideoProfile.STATE_BIDIRECTIONAL);
+                VideoProfile.STATE_BIDIRECTIONAL, null);
         com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
                 .iterator().next();
 
@@ -237,7 +237,7 @@
     public void testOutgoingVideoCallAnsweredAsAudio() throws Exception {
         IdPair ids = startOutgoingPhoneCall("650-555-1212", mPhoneAccountA0.getAccountHandle(),
                 mConnectionServiceFixtureA, Process.myUserHandle(),
-                VideoProfile.STATE_BIDIRECTIONAL);
+                VideoProfile.STATE_BIDIRECTIONAL, null);
         com.android.server.telecom.Call call = mTelecomSystem.getCallsManager().getCalls()
                 .iterator().next();
 
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..c66b0f7
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/VoipCallMonitorTest.java
@@ -0,0 +1,304 @@
+/*
+ * 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.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManagerInternal;
+import android.app.ForegroundServiceDelegationOptions;
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.Person;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.telecom.PhoneAccountHandle;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallState;
+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 NAME = "John Smith";
+    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";
+    public static final String CHANNEL_ID = "TelecomVoipAppChannelId";
+    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 IBinder mServiceConnection;
+
+    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);
+        mMonitor.setActivityManagerInternal(mActivityManagerInternal);
+        mMonitor.startMonitor();
+        when(mActivityManagerInternal.startForegroundServiceDelegate(any(
+                ForegroundServiceDelegationOptions.class), any(ServiceConnection.class)))
+                .thenReturn(true);
+    }
+
+    @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(call1);
+        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));
+    }
+
+    /**
+     * Ensure an app loses foreground service delegation if the user dismisses the call style
+     * notification or the app removes the notification.
+     * Note: post the notification AFTER foreground service delegation is gained
+     */
+    @SmallTest
+    @Test
+    public void testStopFgsIfCallNotificationIsRemoved_PostedAfterFgsIsGained() {
+        // GIVEN
+        StatusBarNotification sbn = createStatusBarNotificationFromHandle(mHandle1User1);
+
+        // WHEN
+        // FGS is gained after the call is added to VoipCallMonitor
+        ServiceConnection c = addCallAndVerifyFgsIsGained(createTestCall("1", mHandle1User1));
+        // simulate an app posting a call style notification after FGS is gained
+        mMonitor.postNotification(sbn);
+
+        // THEN
+        // shortly after posting the notification, simulate the user dismissing it
+        mMonitor.removeNotification(sbn);
+        // FGS should be removed once the notification is removed
+        verify(mActivityManagerInternal, timeout(TIMEOUT)).stopForegroundServiceDelegate(c);
+    }
+
+    /**
+     * Ensure an app loses foreground service delegation if the user dismisses the call style
+     * notification or the app removes the notification.
+     * Note: post the notification BEFORE foreground service delegation is gained
+     */
+    @SmallTest
+    @Test
+    public void testStopFgsIfCallNotificationIsRemoved_PostedBeforeFgsIsGained() {
+        // GIVEN
+        StatusBarNotification sbn = createStatusBarNotificationFromHandle(mHandle1User1);
+
+        // WHEN
+        //  an app posts a call style notification before FGS is gained
+        mMonitor.postNotification(sbn);
+        // FGS is gained after the call is added to VoipCallMonitor
+        ServiceConnection c = addCallAndVerifyFgsIsGained(createTestCall("1", mHandle1User1));
+
+        // THEN
+        // shortly after posting the notification, simulate the user dismissing it
+        mMonitor.removeNotification(sbn);
+        // FGS should be removed once the notification is removed
+        verify(mActivityManagerInternal, timeout(TIMEOUT)).stopForegroundServiceDelegate(c);
+    }
+
+    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());
+        when(call.getState()).thenReturn(CallState.ACTIVE);
+        return call;
+    }
+
+    private Notification createCallStyleNotification() {
+        PendingIntent pendingOngoingIntent = PendingIntent.getActivity(mContext, 0,
+                new Intent(""), PendingIntent.FLAG_IMMUTABLE);
+
+        return new Notification.Builder(mContext,
+                CHANNEL_ID)
+                .setStyle(Notification.CallStyle.forOngoingCall(
+                        new Person.Builder().setName(NAME).setImportant(true).build(),
+                        pendingOngoingIntent)
+                )
+                .setFullScreenIntent(pendingOngoingIntent, true)
+                .build();
+    }
+
+    private StatusBarNotification createStatusBarNotificationFromHandle(PhoneAccountHandle handle) {
+        return new StatusBarNotification(
+                handle.getComponentName().getPackageName(), "", 0, "", 0, 0,
+                createCallStyleNotification(), handle.getUserHandle(), "", 0);
+    }
+
+    private ServiceConnection addCallAndVerifyFgsIsGained(Call call) {
+        ArgumentCaptor<ServiceConnection> captor = ArgumentCaptor.forClass(ServiceConnection.class);
+        // add the call to the VoipCallMonitor under test which will start FGS
+        mMonitor.onCallAdded(call);
+        // FGS should be granted within the timeout
+        verify(mActivityManagerInternal, timeout(TIMEOUT))
+                .startForegroundServiceDelegate(any(
+                                ForegroundServiceDelegationOptions.class),
+                        captor.capture());
+        // onServiceConnected must be called in order for VoipCallMonitor to start monitoring for
+        // a notification before the timeout expires
+        ServiceConnection serviceConnection = captor.getValue();
+        serviceConnection.onServiceConnected(mHandle1User1.getComponentName(), mServiceConnection);
+        return serviceConnection;
+    }
+}
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..0a7e27d
--- /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(VoipCallTransactionTest.this.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"));
+    }
+}