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) {