Merge "Revert "Resolve cross account user ringtone validation."" into main
diff --git a/flags/telecom_anomaly_report_flags.aconfig b/flags/telecom_anomaly_report_flags.aconfig
index 5d42b86..bc248c8 100644
--- a/flags/telecom_anomaly_report_flags.aconfig
+++ b/flags/telecom_anomaly_report_flags.aconfig
@@ -27,3 +27,11 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+# OWNER=tjstuart TARGET=25Q2
+flag {
+  name: "enable_call_exception_anom_reports"
+  namespace: "telecom"
+  description: "When a new CallException is created, generate an anomaly report for metrics"
+  bug: "308932906"
+}
diff --git a/flags/telecom_callaudioroutestatemachine_flags.aconfig b/flags/telecom_callaudioroutestatemachine_flags.aconfig
index 752af8e..b63385f 100644
--- a/flags/telecom_callaudioroutestatemachine_flags.aconfig
+++ b/flags/telecom_callaudioroutestatemachine_flags.aconfig
@@ -162,3 +162,14 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+# OWNER=pmadapurmath TARGET=25Q2
+flag {
+  name: "default_speaker_on_wired_headset_disconnect"
+  namespace: "telecom"
+  description: "Maybe route back into speaker (if it was the previous route) when a wired headset disconnects"
+  bug: "376364368"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 71564e8..f355f04 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Stroom oudio na ander toestel"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Beëindig oproep"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Skakel hier oor"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Kan nie ’n oproep maak nie omdat daar reeds twee oproepe aan die gang is. Beëindig een van die oproepe of voeg dit saam in ’n konferensie voordat ’n nuwe oproep gemaak word."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Kan nie ’n oproep maak nie omdat daar ’n oproep is wat nie aangehou kan word nie. Beëindig die oproep voordat ’n nuwe oproep gemaak word."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Hierdie MMI-kode is nie beskikbaar vir oproepe tussen verskeie rekeninge nie."</string>
 </resources>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index dafbe6e..ee59fc8 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"ኦዲዮን ወደ ሌላ መሣሪያ በመልቀቅ ላይ"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"ዝጋ"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"እዚህ ቀይር"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"ቀድሞውኑ ሁለት ጥሪዎች በሂደት ላይ ስለሆኑ ጥሪ ማድረግ አልተቻለም። አዲስ ጥሪ ከማድረግዎ በፊት ከጥሪዎቹ ላይ የአንዱን ግንኙነት ያቋርጡ ወይም ወደ ጉባዔ ያዋህዷቸው።"</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"ይቆይ ሊደረግ የማይችል ጥሪ በመኖሩ ጥሪ ማድረግ አልተቻለም። አዲስ ጥሪ ከማድረግዎ በፊት የጥሪውን ግንኙነት ያቋርጡ።"</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"ይህ MMI ኮድ በርካታ መለያዎች ላይ ላሉ ጥሪዎች አይገኝም።"</string>
 </resources>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 9eb3a35..2d3c507 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"بث الصوت على جهاز آخر"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"قطع الاتصال"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"الانتقال إلى هنا"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"لا يمكن إجراء مكالمة لأنّ هناك مكالمتين جاريتين حاليًا. يُرجى إنهاء إحدى المكالمتين أو دمجهما في مكالمة جماعية قبل إجراء مكالمة جديدة."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"أنت في مكالمة غير قابلة للتعليق، لذا لا يمكن إجراء مكالمة أخرى. يُرجى إنهاء المكالمة الحالية لإجراء مكالمة جديدة."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"‏لا يتوفّر رمز MMI هذا للمكالمات على مستوى حسابات متعددة."</string>
 </resources>
diff --git a/res/values-as/strings.xml b/res/values-as/strings.xml
index 668f5e5..236a7de 100644
--- a/res/values-as/strings.xml
+++ b/res/values-as/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"অন্য এটা ডিভাইচলৈ অডিঅ’ ষ্ট্ৰীম কৰি থকা হৈছে"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"কলটো কাটি দিয়ক"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"ইয়াত সলনি কৰক"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"কল কৰিব নোৱাৰি, কাৰণ ইতিমধ্যে দুটা কল চলি আছে। এটা নতুন কল কৰাৰ আগতে সেই দুটা কলৰ এটাৰ সংযোগ বিচ্ছিন্ন কৰক বা কল দুটা একত্ৰিত কৰি এটা কনফাৰেন্স কললৈ সলনি কৰক।"</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"হ’ল্ডত ৰাখিব নোৱাৰা কল এটা চলি থকাৰ বাবে কল কৰিব নোৱাৰি। এটা নতুন কল কৰাৰ আগেয়ে কলটোৰ সংযোগ বিচ্ছিন্ন কৰক।"</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"একাধিক একাউণ্টৰ মাজত কল কৰাৰ বাবে এই MMI ক’ডটো উপলব্ধ নহয়।"</string>
 </resources>
diff --git a/res/values-az/strings.xml b/res/values-az/strings.xml
index c975159..d60ff50 100644
--- a/res/values-az/strings.xml
+++ b/res/values-az/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Audio digər cihaza ötürülür"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Zəngi sonlandırın"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Buraya keçin"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Davam edən iki zəng olduğuna görə zəng etmək mümkün deyil. Yeni zəng etməzdən əvvəl zənglərin birini dayandırın və ya onları konfransa birləşdirin."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Canlı zəngi dayandırmaq mümkün olmadığına görə yeni zəng etmək olmur. Yeni zəng etməzdən əvvəl digər zəngi dayandırın."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Bu MMI kodu birdən çox hesab üzrə zənglər üçün əlçatan deyil."</string>
 </resources>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index 3709c25..dc5fb31 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Zvuk se strimuje na drugi uređaj"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Prekini vezu"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Prebaci ovde"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Ne možete da uputite poziv jer su dva poziva već u toku. Prekinite jedan od njih ili ih objedinite u konferenciju da biste uputili novi poziv."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Ne možete da uputite poziv jer je u toku poziv koji ne može da se stavi na čekanje. Prekinite taj poziv pre upućivanja novog poziva."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Ovaj MMI kôd nije dostupan za pozive na više naloga."</string>
 </resources>
diff --git a/res/values-be/strings.xml b/res/values-be/strings.xml
index c3c6e2f..2597a3a 100644
--- a/res/values-be/strings.xml
+++ b/res/values-be/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Перадача аўдыя плынню на іншую прыладу"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Завяршыць выклік"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Пераключыцца"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Немагчыма зрабіць новы выклік, бо ўжо выконваюцца два іншыя. Каб зрабіць новы выклік, завяршыце адзін з бягучых ці аб’яднайце іх у канферэнц-выклік."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Немагчыма зрабіць выклік, бо ўжо выконваецца выклік, які нельга пераключыць у рэжым утрымання. Перш чым зрабіць новы выклік, завяршыце актыўны."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Гэты код MMI недаступны для выклікаў паміж некалькімі ўліковымі запісамі."</string>
 </resources>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index 116c884..66308a4 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Звукът се предава поточно към друго устройство"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Затваряне"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Превключете тук"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Не може да се извърши обаждане, тъй като вече се провеждат две обаждания. Прекъснете едно от тях или ги обединете в конферентен разговор, преди да извършите ново обаждане."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Не може да се извърши обаждане, тъй като има обаждане, което не може да бъде поставено на изчакване. Прекъснете обаждането, преди да извършите ново."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Този MMI код не е налице, докато се провежда обаждане в друг профил."</string>
 </resources>
diff --git a/res/values-bn/strings.xml b/res/values-bn/strings.xml
index 4f4fea6..a2a11d4 100644
--- a/res/values-bn/strings.xml
+++ b/res/values-bn/strings.xml
@@ -134,4 +134,8 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"অন্য ডিভাইসে অডিও স্ট্রিম করা হচ্ছে"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"কল কেটে দিন"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"এখানে পাল্টান"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"দুটি কল চলছে, তাই আরেকটি কল করা যাচ্ছে না। নতুন কল করার আগে যেকোনও একটি কল কেটে দিন অথবা দুটিকে একসাথে একটি কনফারেন্সে মার্জ করুন।"</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"হোল্ড করা যাবে না এমন কল রয়েছে তাই আরেকটি কল করা যাচ্ছে না। নতুন কল করার আগে কলটি ডিসকানেক্ট করুন।"</string>
+    <!-- no translation found for callFailed_reject_mmi (5219280796733595167) -->
+    <skip />
 </resources>
diff --git a/res/values-bs/strings.xml b/res/values-bs/strings.xml
index ba75d0c..3e1bd68 100644
--- a/res/values-bs/strings.xml
+++ b/res/values-bs/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Prenos zvuka na drugom uređaju"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Prekini vezu"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Prebaci ovdje"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Nije moguće uputiti poziv jer su dva poziva već u toku. Prekinite jedan od njih ili ih spojite u konferencijski poziv prije upućivanja novog poziva."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Nije moguće uputiti poziv zbog poziva koji se ne može staviti na čekanje. Prekinite taj poziv prije upućivanja novog poziva."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"MMI kôd nije dostupan za pozive na više računa."</string>
 </resources>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 5793449..185f458 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"S\'està reproduint àudio en continu en un altre dispositiu"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Penja"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Canvia aquí"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"No es pot fer la trucada perquè ja n\'hi ha dues en curs. Desconnecta\'n una o combina-les en una conferència abans de fer-ne més de noves."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"No es pot fer la trucada perquè n\'hi ha una que no es pot posar en espera. Desconnecta-la abans de fer-ne més de noves."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Aquest codi MMI no es pot utilitzar per fer trucades amb diversos comptes."</string>
 </resources>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 0619308..976fa83 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Streamování zvuku do druhého zařízení"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Zavěsit"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Přepnout sem"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Nemůžete uskutečnit hovor, protože už probíhají dva hovory. Než zahájíte nový hovor, jeden ze stávajících odpojte nebo je slučte do konference."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Nemůžete uskutečnit hovor, protože už probíhá hovor, který nelze podržet. Než zahájíte nový hovor, odpojte ten předchozí."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Tento kód MMI není k dispozici pro hovory ve více účtech."</string>
 </resources>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index 0eb69ff..7e33fc1 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Streamer lyd til en anden enhed"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Læg på"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Skift hertil"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Der kan ikke foretages et opkald, fordi der allerede er to igangværende opkald. Afslut et af opkaldene, eller flet dem til et telefonmøde, før du foretager et nyt opkald."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Opkaldet kan ikke foretages, fordi der er et opkald i gang, som ikke kan sættes på hold. Afslut opkaldet, før du foretager et nyt."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Denne MMI-kode er ikke tilgængelig for opkald på tværs af flere konti."</string>
 </resources>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 665124a..8444610 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Audio auf einem anderen Gerät streamen"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Anruf beenden"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Auf dieses Gerät wechseln"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Anruf nicht möglich, weil bereits zwei Anrufe aktiv sind. Beende einen der Anrufe oder führe beide Anrufe in einer Telefonkonferenz zusammen, bevor du einen neuen Anruf startest."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Anruf nicht möglich, da ein Anruf nicht gehalten werden kann. Beende den Anruf, bevor du einen neuen Anruf startest."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Dieser MMI-Code ist nicht für Anrufe mit mehreren Konten verfügbar."</string>
 </resources>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index ba504d7..2e4170e 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Ροή ήχου σε άλλη συσκευή"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Απόρριψη"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Εναλλαγή εδώ"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Δεν είναι δυνατή η πραγματοποίηση κλήσης, επειδή υπάρχουν ήδη δύο κλήσεις σε εξέλιξη. Τερματίστε μια από τις κλήσεις ή συγχωνεύστε τις σε μια διάσκεψη, προτού πραγματοποιήσετε νέα κλήση."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Δεν είναι δυνατή η πραγματοποίηση κλήσης, επειδή υπάρχει κλήση που δεν μπορεί να τεθεί σε αναμονή. Τερματίστε την κλήση πριν πραγματοποιήσετε νέα κλήση."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Αυτός ο κωδικός MMI δεν είναι διαθέσιμος για κλήσεις σε πολλούς λογαριασμούς."</string>
 </resources>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 1ce62df..ae97b7b 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Streaming audio to other device"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Hang up"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Switch here"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Cannot place a call as there are already two calls in progress. Disconnect one of the calls or merge them into a conference prior to placing a new call."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Cannot place a call as there is an unholdable call. Disconnect the call prior to placing a new call."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"This MMI code is not available for calls across multiple accounts."</string>
 </resources>
diff --git a/res/values-en-rCA/strings.xml b/res/values-en-rCA/strings.xml
index 8ae9c0a..5e4c8dd 100644
--- a/res/values-en-rCA/strings.xml
+++ b/res/values-en-rCA/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Streaming audio to other device"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Hang up"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Switch here"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Cannot place a call as there are already two calls in progress. Disconnect one of the calls or merge them into a conference prior to placing a new call."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Cannot place a call as there is an unholdable call. Disconnect the call prior to placing a new call."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"This MMI code is not available for calls across multiple accounts."</string>
 </resources>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 1ce62df..ae97b7b 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Streaming audio to other device"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Hang up"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Switch here"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Cannot place a call as there are already two calls in progress. Disconnect one of the calls or merge them into a conference prior to placing a new call."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Cannot place a call as there is an unholdable call. Disconnect the call prior to placing a new call."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"This MMI code is not available for calls across multiple accounts."</string>
 </resources>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 1ce62df..ae97b7b 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Streaming audio to other device"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Hang up"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Switch here"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Cannot place a call as there are already two calls in progress. Disconnect one of the calls or merge them into a conference prior to placing a new call."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Cannot place a call as there is an unholdable call. Disconnect the call prior to placing a new call."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"This MMI code is not available for calls across multiple accounts."</string>
 </resources>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index 668a696..d31a1cf 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Transmitiendo el audio a otro dispositivo"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Colgar"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Cambiar aquí"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"No puedes realizar la llamada porque hay otras dos en curso. Finaliza una de ellas o combínalas en una conferencia antes de iniciar una nueva."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"No puedes realizar la llamada porque hay otra que no se puede mantener en espera. Finalízala antes de iniciar una nueva."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Este código MMI no está disponible para llamadas en varias cuentas."</string>
 </resources>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index 96163b3..f404e98 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Transmitiendo audio a otro dispositivo"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Colgar"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Cambiar aquí"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"No se puede llamar porque ya hay dos llamadas en curso. Interrumpe una de ellas o combínalas en una conferencia antes de hacer otra llamada."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"No se puede hacer una llamada porque ya hay otra que no se puede poner en espera. Interrumpe la llamada antes de hacer otra."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Este código MMI no está disponible para hacer llamadas con varias cuentas."</string>
 </resources>
diff --git a/res/values-et/strings.xml b/res/values-et/strings.xml
index 6fd5592..ac2f2ef 100644
--- a/res/values-et/strings.xml
+++ b/res/values-et/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Heli voogesitamine teise seadmesse"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Lõpeta kõne"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Vaheta siia"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Kõnet ei saa teha, kuna kaks kõnet on juba pooleli. Enne uue kõne tegemist katkestage üks kõnedest või liitke need konverentskõneks."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Uut kõnet ei saa teha, kuna pooleliolevat kõnet ei saa ootele panna. Enne uue kõne tegemist katkestage pooleliolev kõne."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"See MMI-kood pole saadaval mitmel kontol toimuvate kõnede jaoks."</string>
 </resources>
diff --git a/res/values-eu/strings.xml b/res/values-eu/strings.xml
index 3efbc07..7e871f6 100644
--- a/res/values-eu/strings.xml
+++ b/res/values-eu/strings.xml
@@ -134,4 +134,8 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Audioa beste gailu batera igortzen ari da"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Amaitu deia"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Aldatu hona"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Ezin da egin deia, dagoeneko 2 dei daudelako abian. Beste dei bat egin aurretik, eten deietako bat edo bateratu deiak konferentzia-dei bakarrean."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Ezin da egin deia, zain utzi ezin den dei bat abian delako. Deskonektatu dei hori beste dei bat egin ahal izateko."</string>
+    <!-- no translation found for callFailed_reject_mmi (5219280796733595167) -->
+    <skip />
 </resources>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 6bd2ff6..5ff3318 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"درحال جاری‌سازی صدا به دستگاه دیگر"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"قطع تماس"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"انتقال در اینجا انجام شود"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"نمی‌توانید تماسی برقرار کنید، زیرا هم‌اکنون دو تماس دیگر درحال انجام است. قبل‌از برقراری تماس جدید، یکی از تماس‌ها را قطع کنید یا آن‌ها را به‌صورت کنفرانسی ادغام کنید."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"نمی‌توانید تماسی برقرار کنید زیرا هم‌اکنون تماسی بدون قابلیت انتظار درحال انجام است. قبل‌از برقراری تماس جدید، تماس را قطع کنید."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"‏این کد MMI برای تماس در چندین حساب دردسترس نیست."</string>
 </resources>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 0d5fdbb..4406524 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Audiota striimataan toiselle laitteelle"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Lopeta puhelu"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Vaihda puhelimeen"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Puhelua ei voi soittaa, koska kaksi puhelua on jo käynnissä. Katkaise toinen puheluista tai yhdistä ne puhelinneuvotteluksi ennen uuden puhelun soittamista."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Puhelua ei voi soittaa, koska puhelua ei voi asettaa pitoon. Katkaise puhelu ennen uuden puhelun soittamista."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"MMI-koodi ei ole käytettävissä useilla tileillä käytävissä puheluissa."</string>
 </resources>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index cfd153b..28e0f7c 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Diffusion audio en continu vers un autre appareil en cours…"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Raccrocher"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Revenir à cet appareil"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Impossible de passer un appel parce que deux appels sont déjà en cours. Déconnectez-en un ou fusionnez-les en conférence téléphonique avant de passer un nouvel appel."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Impossible de passer un appel parce qu\'un appel impossible à mettre en attente est en cours. Débranchez l\'appel avant de passer un nouvel appel."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Ce code IHM n\'est pas disponible pour les appels utilisant plusieurs comptes."</string>
 </resources>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index 9dbca8f..b45a4b1 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Streaming de l\'audio sur un autre appareil"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Raccrocher"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Passer ici"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Impossible de passer un appel, car deux appels sont déjà en cours. Mettez fin à l\'un des appels ou fusionnez-les afin de créer une conférence avant de passer un nouvel appel."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Impossible de passer un appel, car un appel est en cours et ne peut pas être mis en attente. Mettez fin à l\'appel avant de passer un nouvel appel."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Ce code IHM n\'est pas disponible pour les appels sur plusieurs comptes."</string>
 </resources>
diff --git a/res/values-gl/strings.xml b/res/values-gl/strings.xml
index f8eb32c..5c1703f 100644
--- a/res/values-gl/strings.xml
+++ b/res/values-gl/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Emitindo audio noutro dispositivo"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Colgar"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Volver aquí"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Non se pode facer ningunha chamada porque xa hai dúas en curso. Para poder facer unha nova, desconecta unha desas dúas ou combínaas nunha conferencia."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Non se pode facer ningunha chamada porque hai unha que non é posible poñer en espera. Desconéctaa para poder facer unha nova."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Este código MMI non está dispoñible para chamadas en varias contas."</string>
 </resources>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index dd04bcf..036575f 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"ઑડિયોને અન્ય ડિવાઇસ પર સ્ટ્રીમ કરી રહ્યાં છીએ"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"સમાપ્ત કરો"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"અહીં સ્વિચ કરો"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"કૉલ કરી શકાતો નથી, કારણ કે બે કૉલ પહેલેથી ચાલુ છે. કોઈ નવો કૉલ કરતા પહેલાં તેમાંના એક કૉલને ડિસ્કનેક્ટ કરો અથવા તેમને કોઈ કૉન્ફરન્સમાં મર્જ કરો."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"કૉલ કરી શકાતો નથી, કારણ કે હોલ્ડ ન કરી શકાય તેવો કોઈ કૉલ ચાલુ છે. કોઈ નવો કૉલ કરતા પહેલાં કૉલને ડિસ્કનેક્ટ કરો."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"આ MMI કોડનો ઉપયોગ એકથી વધુ એકાઉન્ટ પર ચાલી રહેલા કૉલ માટે ઉપલબ્ધ નથી."</string>
 </resources>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 683a5ab..7f8cd6b 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"ऑडियो को दूसरे डिवाइस पर स्ट्रीम किया जा रहा है"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"कॉल खत्म करें"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"यहां स्विच करें"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"कॉल नहीं किया जा सका, क्योंकि पहले से ही दो कॉल जारी हैं. नया कॉल करने से पहले, उनमें से किसी एक कॉल को बंद करें या उन्हें कॉन्फ़्रेंस कॉल में मर्ज करें."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"कॉल नहीं किया जा सकता, क्योंकि पहले से चल रहे कॉल को होल्ड नहीं किया जा सकता. नया कॉल करने से पहले, मौजूदा कॉल को डिसकनेक्ट करें."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"किसी दूसरे खाते पर चल रहे कॉल के दौरान, इस एमएमआई कोड का इस्तेमाल नहीं किया जा सकता."</string>
 </resources>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index b664e5c..e924390 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Streaming zvuka na drugi uređaj"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Prekini vezu"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Promijeni ovdje"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Poziv se ne može uputiti jer već su dva poziva u tijeku. Prije upućivanja novog poziva prekinite jedan od ta dva poziva ili ih spojite u konferencijski poziv."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Poziv se ne može uputiti jer je u tijeku poziv koji se ne može zadržati. Prekinite taj poziv prije upućivanja novog."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Taj MMI kôd nije dostupan za pozive na više računa."</string>
 </resources>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 0a0c377..9bacb81 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Hang átvitele másik eszközre"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Hívás befejezése"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Váltás itt"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Nem kezdeményezhet hívást, mert már két hívás van folyamatban. Mielőtt új hívást indítana, tegye le az egyiket, vagy egyesítse őket egy konferenciahívásban."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Nem kezdeményezhet hívást, mert folyamatban van egy nem tartható hívás. Mielőtt új hívást indítana, szakítsa meg a hívást."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Ez az MMI-kód nem áll rendelkezésre hívásokhoz több fiók használata esetén."</string>
 </resources>
diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml
index 7f877c5..79383a6 100644
--- a/res/values-hy/strings.xml
+++ b/res/values-hy/strings.xml
@@ -134,4 +134,8 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Աուդիոյի հեռարձակում այլ սարքում"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Ավարտել զանգը"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Անցնել այստեղ"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Հնարավոր չէ զանգել, քանի որ արդեն երկու ընթացիկ զանգ կա։ Նախքան նոր զանգ կատարելը ավարտեք զանգերից մեկը կամ միավորեք դրանք մեկ խմբային զանգում։"</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Հնարավոր չէ զանգել, քանի որ ընթացիկ զանգը չի կարելի սպասման մեջ դնել։ Նախքան նոր զանգ կատարելը պատասխանեք ավարտեք այս զանգը։"</string>
+    <!-- no translation found for callFailed_reject_mmi (5219280796733595167) -->
+    <skip />
 </resources>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 34c0c66..4da7a06 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Streaming audio ke perangkat lain"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Akhiri"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Beralih ke sini"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Tidak dapat melakukan panggilan karena ada dua panggilan yang sedang berlangsung. Putuskan salah satu panggilan atau gabungkan keduanya menjadi satu konferensi sebelum melakukan panggilan baru."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Tidak dapat melakukan panggilan karena ada panggilan yang tidak dapat ditahan. Putuskan panggilan sebelum melakukan panggilan baru."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Kode MMI ini tidak tersedia untuk panggilan di beberapa akun."</string>
 </resources>
diff --git a/res/values-is/strings.xml b/res/values-is/strings.xml
index c2fcf8f..16f8a7b 100644
--- a/res/values-is/strings.xml
+++ b/res/values-is/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Streymir hljóði í annað tæki"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Leggja á"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Skipta hingað"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Ekki er hægt að hringja símtal vegna þess að þegar eru tvö símtöl í gangi. Aftengdu annað símtalið eða sameinaðu þau í símafund áður en þú hringir nýtt símtal."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Ekki er hægt að hringja símtal vegna símtals sem ekki er hægt að setja í bið. Slíttu símtalinu áður en þú hringir nýtt símtal."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Þessi MMI-kóði er ekki í boði ef hringt er í marga reikninga."</string>
 </resources>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index 42cb0c8..76d5927 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Streaming audio all\'altro dispositivo"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Riaggancia"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Passa qui"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Impossibile effettuare una chiamata perché due chiamate sono già in corso. Unisci le chiamate in una conferenza o scollegane una prima di effettuare una nuova chiamata."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Impossibile effettuare una chiamata perché è presente una chiamata non bloccabile. Termina la chiamata prima di effettuarne una nuova."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Questo codice MMI non è disponibile per le chiamate su più account."</string>
 </resources>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 98c5347..45f3885 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"הקול מושמע במכשיר אחר"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"ניתוק"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"העברת השיחה בחזרה לטלפון"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"אי אפשר להתקשר כי כבר יש שתי שיחות פעילות. צריך לנתק את אחת מהשיחות או למזג אותן לשיחת ועידה ורק אז לנסות להתקשר למספר אחר."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"אי אפשר להתקשר כי כבר יש שיחה פעילה ואי אפשר להעביר אותה להמתנה. צריך לנתק את השיחה ורק אז לנסות להתקשר למספר אחר."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"‏אי אפשר להשתמש בקוד ה-MMI הזה לשיחות במספר חשבונות."</string>
 </resources>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 2df6736..dcc48ad 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"他のデバイスに音声をストリーミングしています"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"通話を終了"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"このデバイスに切り替える"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"進行中の通話がすでに 2 件あるため、新しく通話を発信することはできません。進行中の通話のどちらかを終了するか、2 件の通話を統合してグループ通話にすると、新しく通話を発信できるようになります。"</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"保留できない通話があるため、新しく通話を発信できません。通話を終了すると、新しく通話を発信できるようになります。"</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"この MMI コードは、複数のアカウントにまたがる通話には使用できません。"</string>
 </resources>
diff --git a/res/values-ka/strings.xml b/res/values-ka/strings.xml
index f2a3e90..91fd298 100644
--- a/res/values-ka/strings.xml
+++ b/res/values-ka/strings.xml
@@ -134,4 +134,8 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"მიმდინარეობს აუდიოს სტრიმინგი სხვა მოწყობილობაზე"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"გათიშვა"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"გადართვა"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"ზარის განხორციელება შეუძლებელია, რადგან უკვე ორი ზარი მიმდინარეობს. ახალი ზარის განსახორციელებლად გათიშეთ ერთ-ერთი ზარი ან გააერთიანეთ ისინი კონფერენციად."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"ზარის განხორციელება ვერ ხერხდება, რადგან მიმდინარეობს ზარი, რომლის მოცდის რეჟიმში გადაყვანაც შეუძლებელია. ახალი ზარის განსახორციელებლად გათიშეთ აღნიშნული ზარი."</string>
+    <!-- no translation found for callFailed_reject_mmi (5219280796733595167) -->
+    <skip />
 </resources>
diff --git a/res/values-kk/strings.xml b/res/values-kk/strings.xml
index 22ac1fc..0764085 100644
--- a/res/values-kk/strings.xml
+++ b/res/values-kk/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Аудионы басқа құрылғыға трансляциялау"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Қоңырауды аяқтау"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Осы жерде ауысу"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Қоңырау шалу мүмкін емес, себебі онсыз да екі қоңырау жүріп жатыр. Жаңа қоңырау бастау үшін олардың біреуін тоқтатыңыз немесе екеуін бір конференцияға біріктіріңіз."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Қоңырау шалу мүмкін емес, себебі жүріп жатқан қоңырау кідіртілмейді. Жаңадан қоңырау шалу үшін жүріп жатқан қоңырауды тоқтатыңыз."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Бұл MMI кодын бірнеше аккаунттағы қоңыраулар үшін пайдалану мүмкін емес."</string>
 </resources>
diff --git a/res/values-km/strings.xml b/res/values-km/strings.xml
index 41b02f3..ff8b49d 100644
--- a/res/values-km/strings.xml
+++ b/res/values-km/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"កំពុង​ផ្សាយ​សំឡេង​ទៅឧបករណ៍​ផ្សេងទៀត"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"បញ្ចប់​ការហៅ​ទូរសព្ទ"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"ប្ដូរនៅទីនេះ"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"មិន​អាច​ធ្វើ​ការហៅ​ទូរសព្ទ​បាន​ទេ ដោយសារ​មាន​ការហៅ​ទូរសព្ទ​ពីរ​កំពុង​ដំណើរការ​រួច​ហើយ។ ផ្ដាច់​ការហៅ​ទូរសព្ទ​មួយ ឬ​ដាក់​ការហៅ​ទូរសព្ទ​ទាំងនេះ​ចូល​គ្នា​ជា​ការហៅជាក្រុម មុន​នឹង​ធ្វើ​ការហៅ​ទូរសព្ទ​ថ្មី។"</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"មិនអាច​ធ្វើការហៅ​ទូរសព្ទ​បានទេ ដោយសារ​មានការហៅ​ទូរសព្ទ​ដែលមិនអាច​ដាក់ឱ្យ​រង់ចាំបាន។ សូមផ្ដាច់​ការហៅ​ទូរសព្ទ​នោះសិន មុនពេល​ធ្វើការហៅ​ទូរសព្ទថ្មី។"</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"មិនអាចប្រើលេខកូដ MMI នេះសម្រាប់ការហៅទូរសព្ទនៅលើ​គណនី​ច្រើនបានទេ។"</string>
 </resources>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index da7fef8..03f1c31 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"ಇತರ ಸಾಧನಕ್ಕೆ ಆಡಿಯೊವನ್ನು ಸ್ಟ್ರೀಮ್ ಮಾಡಲಾಗುತ್ತಿದೆ"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"ಹ್ಯಾಂಗ್ ಅಪ್"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"ಇಲ್ಲಿಗೆ ಬದಲಾಯಿಸಿ"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"ಈಗಾಗಲೇ ಎರಡು ಕರೆಗಳು ಪ್ರಗತಿಯಲ್ಲಿರುವುದರಿಂದ, ಕರೆ ಮಾಡಲು ಸಾಧ್ಯವಾಗುವುದಿಲ್ಲ. ಒಂದು ಕರೆಯನ್ನು ಡಿಸ್‌ಕನೆಕ್ಟ್ ಮಾಡಿ ಅಥವಾ ಹೊಸ ಕರೆಯನ್ನು ಮಾಡುವ ಮೊದಲು ಎರಡು ಕರೆಗಳನ್ನು ಒಂದೇ ಕಾನ್ಫರೆನ್ಸ್‌ನಲ್ಲಿ ವಿಲೀನಗೊಳಿಸಿ."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"ಈಗಾಗಲೇ ಪ್ರಗತಿಯಲ್ಲಿರುವ ಕರೆಯನ್ನು ಹೋಲ್ಡ್ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲದ ಕಾರಣ, ಕರೆ ಮಾಡಲು ಸಾಧ್ಯವಿಲ್ಲ. ಹೊಸ ಕರೆಯನ್ನು ಮಾಡುವ ಮೊದಲು ಕರೆಯನ್ನು ಡಿಸ್‌ಕನೆಕ್ಟ್ ಮಾಡಿ."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"ಬಹು ಖಾತೆಗಳಾದ್ಯಂತ ಕರೆಗಳಿಗೆ ಈ MMI ಕೋಡ್ ಲಭ್ಯವಿರುವುದಿಲ್ಲ."</string>
 </resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index f0b95fd..bf95a02 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"다른 기기로 오디오 스트리밍"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"전화 끊기"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"현재 기기로 전환"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"이미 진행 중인 두 건의 통화가 있으므로 전화를 걸 수 없습니다. 새로 전화를 걸기 전에 통화 중 하나를 연결 해제하거나 두 통화를 다자간 통화로 병합하세요."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"대기할 수 없는 통화가 있으므로 전화를 걸 수 없습니다. 새로 전화를 걸기 전에 통화를 종료하세요."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"이 MMI 코드는 여러 계정 간에 통화에 사용할 수 없습니다."</string>
 </resources>
diff --git a/res/values-ky/strings.xml b/res/values-ky/strings.xml
index ad19dd7..41ddb11 100644
--- a/res/values-ky/strings.xml
+++ b/res/values-ky/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Аудио башка түзмөккө берилүүдө"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Чалууну бүтүрүү"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Бул жерге которулуу"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Чалуу аткарылбайт, анткени эки чалуу аткарылууда. Бир чалууну өчүрүңүз же аларды конференцияга бириктириңиз."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Чалуу мүмкүн эмес, анткени кармалбаган чалуу бар. Жаңы чалуудан мурда учурдагыны бүтүрүңүз."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Бул MMI коду бир нече аккаунт аркылуу чалуулар үчүн жеткиликсиз."</string>
 </resources>
diff --git a/res/values-lo/strings.xml b/res/values-lo/strings.xml
index 8e43935..60a6218 100644
--- a/res/values-lo/strings.xml
+++ b/res/values-lo/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"ສະຕຣີມສຽງໄປໃສ່ອຸປະກອນອື່ນ"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"ວາງສາຍ"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"ສະຫຼັບບ່ອນນີ້"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"ບໍ່ສາມາດໂທໄດ້ເນື່ອງຈາກມີສອງສາຍກຳລັງໂທຢູ່. ກະລຸນາຕັດການເຊື່ອມຕໍ່ສາຍໃດໜຶ່ງອອກ ຫຼື ຮວມສາຍເປັນການປະຊຸມທາງໂທລະສັບກ່ອນໂທໃໝ່."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"ບໍ່ສາມາດໂທໄດ້ເນື່ອງຈາກມີການໂທທີ່ບໍ່ສາມາດຖືສາຍຄ້າງໄວ້ໄດ້. ຕັດການເຊື່ອມຕໍ່ສາຍກ່ອນໂທໃໝ່."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"ລະຫັດ MMI ນີ້ແມ່ນໃຊ້ບໍ່ໄດ້ສໍາລັບການໂທດ້ວຍຫຼາຍບັນຊີ."</string>
 </resources>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 04f4c96..84a6c23 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Srautinis garso perdavimas į kitą įrenginį"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Baigti skambutį"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Perjungti čia"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Negalite skambinti, nes jau dalyvaujate dviejuose skambučiuose. Prieš pradėdami naują skambutį užbaikite vieną iš skambučių arba sujunkite juos į konferenciją."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Negalite skambinti, nes yra skambutis, kurio negalima sulaikyti. Prieš pradėdami naują skambutį nutraukite esamą."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Šis MMI kodas nepasiekiamas skambučiuose keliose paskyrose."</string>
 </resources>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index ee807da..88cdc4c 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Notiek audio straumēšana uz citu ierīci."</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Beigt zvanu"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Pārslēgties šeit"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Nevar veikt zvanu, jo pašlaik jau notiek divi zvani. Pirms jauna zvana veikšanas pārtrauciet vienu no pašreizējiem zvaniem vai apvienojiet tos konferences zvanā."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Nevar veikt zvanu, jo pašlaik notiek zvans, ko nevar pārtraukt. Pirms jauna zvana veikšanas pārtrauciet pašreizējo zvanu."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Šis MMI kods nav pieejams zvaniem vairākos kontos."</string>
 </resources>
diff --git a/res/values-mk/strings.xml b/res/values-mk/strings.xml
index 0f6e41f..faf175f 100644
--- a/res/values-mk/strings.xml
+++ b/res/values-mk/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Звукот се стримува на друг уред"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Спушти"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Префрли овде"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Не може да се воспостави повик бидејќи веќе се во тек два повика. Исклучете го едниот од повиците или спојте ги во конференциски повик пред да воспоставите нов повик."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Не може да се воспостави повик бидејќи има повик што не може да се стави на чекање. Исклучете го повикот пред да воспоставите нов повик."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"MMI-кодов не е достапен за повици на повеќе сметки."</string>
 </resources>
diff --git a/res/values-ml/strings.xml b/res/values-ml/strings.xml
index 1301b44..91cbdae 100644
--- a/res/values-ml/strings.xml
+++ b/res/values-ml/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"ഓഡിയോ മറ്റൊരു ഉപകരണത്തിലേക്ക് സ്‌ട്രീം ചെയ്യുന്നു"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"മാറ്റി വയ്‌ക്കുക"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"ഇവിടേക്ക് മാറുക"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"രണ്ട് കോളുകൾ നിലവിൽ പുരോഗമിക്കുന്നതിനാൽ, ഇനിയൊരു കോൾ കൂടി ചെയ്യാനാകില്ല. പുതിയൊരു കോൾ ചെയ്യുന്നതിന് മുമ്പ്, കോളുകളിലൊരെണ്ണം വിച്ഛേദിക്കുകയോ അവ കോൺഫറൻസ് കോളായി ലയിപ്പിക്കുകയോ ചെയ്യുക."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"ഹോൾഡ് ചെയ്യാനാകാത്ത കോൾ പുരോഗമിക്കുന്നതിനാൽ ഇനിയൊരു കോൾ കൂടി ചെയ്യാനാകില്ല. പുതിയൊരു കോൾ ചെയ്യുന്നതിന് മുമ്പ് കോൾ വിച്ഛേദിക്കുക."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"ഒന്നിലധികം അക്കൗണ്ടുകളിലുടനീളം കോളുകൾക്ക് ഈ MMI കോഡ് ലഭ്യമല്ല."</string>
 </resources>
diff --git a/res/values-mn/strings.xml b/res/values-mn/strings.xml
index 0b26e7e..5eb5594 100644
--- a/res/values-mn/strings.xml
+++ b/res/values-mn/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Бусад төхөөрөмж рүү аудио дамжуулж байна"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Таслах"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Ийшээ сэлгэх"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Аль хэдийн хоёр дуудлага хийж байгаа тул дуудлага хийх боломжгүй байна. Шинэ дуудлага хийхийн өмнө аль нэг дуудлагыг салгах эсвэл тэдгээрийг хурал болгож нэгтгэнэ үү."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Хүлээлгэх боломжгүй дуудлага байгаа тул дуудлага хийх боломжгүй. Шинэ дуудлага хийхээсээ өмнө тухайн дуудлагыг салгана уу."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Энэ MMI код нь олон бүртгэл дээрх дуудлагад боломжгүй."</string>
 </resources>
diff --git a/res/values-mr/strings.xml b/res/values-mr/strings.xml
index eca7b4d..ddf6456 100644
--- a/res/values-mr/strings.xml
+++ b/res/values-mr/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"ऑडिओ हा दुसऱ्या डिव्हाइसवर स्ट्रीम करत आहे"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"बंद करा"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"येथे स्विच करा"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"दोन कॉल आधीच प्रगतीपथावर असल्यामुळे कॉल करू शकत नाही. नवीन कॉल करण्याआधी त्यांपैकी एक कॉल डिस्कनेक्ट करा किंवा त्यांना कॉन्फरन्स कॉलमध्ये मर्ज करा."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"होल्डवर न ठेवता येणारा कॉल असल्यामुळे, कॉल करू शकत नाही. नवीन कॉल करण्यापूर्वी, कॉल डिस्कनेक्ट करा."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"हा MMI कोड एकाहून अधिक खात्यांवरील कॉलसाठी उपलब्ध नाही."</string>
 </resources>
diff --git a/res/values-ms/strings.xml b/res/values-ms/strings.xml
index ebfffd0..5ad1b8d 100644
--- a/res/values-ms/strings.xml
+++ b/res/values-ms/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Penstriman audio pada peranti lain"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Tamatkan panggilan"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Tukar di sini"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Tidak dapat membuat panggilan kerana sudah terdapat dua panggilan yang sedang berlangsung. Putuskan satu daripada panggilan itu atau gabungkan panggilan tersebut menjadi persidangan sebelum membuat panggilan baharu."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Tidak dapat membuat panggilan kerana terdapat panggilan yang sedang menunggu. Putuskan panggilan sebelum membuat panggilan baharu."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Kod MMI ini tidak tersedia untuk panggilan merentas berbilang akaun."</string>
 </resources>
diff --git a/res/values-my/strings.xml b/res/values-my/strings.xml
index e7f0fd4..653eb3e 100644
--- a/res/values-my/strings.xml
+++ b/res/values-my/strings.xml
@@ -134,4 +134,8 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"အသံကို အခြားစက်တွင် တိုက်ရိုက်လွှင့်နေသည်"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"ဖုန်းချရန်"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"ဤနေရာသို့ လွှဲပြောင်းရန်"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"ဖုန်းခေါ်ဆိုမှုနှစ်ခုကို ပြုလုပ်နေသဖြင့် ဖုန်းထပ်ခေါ်၍မရပါ။ ခေါ်ဆိုမှုအသစ် မပြုလုပ်မီ ၎င်းတို့အနက် တစ်ခုကို ဖုန်းချပါ (သို့) အစည်းအဝေးအဖြစ် ပေါင်းစည်းပါ။"</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"ဆိုင်းငံ့၍မရသော ခေါ်ဆိုမှုရှိနေသဖြင့် ဖုန်းထပ်ခေါ်၍မရပါ။ ခေါ်ဆိုမှုအသစ် မပြုလုပ်မီ ဤခေါ်ဆိုမှုကို ဖုန်းချပါ။"</string>
+    <!-- no translation found for callFailed_reject_mmi (5219280796733595167) -->
+    <skip />
 </resources>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index 66e6ffc..89f0796 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Strømmer lyden til en annen enhet"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Legg på"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Flytt hit"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Du kan ikke ringe fordi to andre anrop allerede pågår. Koble fra ett av anropene eller slå dem sammen i en konferansesamtale, før du ringer på nytt."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Kan ikke ringe fordi det pågår en samtale som ikke kan settes på vent. Avslutt samtalen før du ringer på nytt."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Denne MMI-koden kan ikke brukes til anrop på flere kontoer samtidig."</string>
 </resources>
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index 4aeceef..cbf00df 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"अर्को डिभाइसमा अडियो स्ट्रिम गरिँदै छ"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"कल काट्नुहोस्"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"यहाँ गई बदल्नुहोस्"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"दुई वटा कल चलिरहेका हुनाले नयाँ कल गर्न सकिँदैन। नयाँ कल गर्नुअघि दुईमध्ये एउटा कल डिस्कनेक्ट गर्नुहोस् वा तिनलाई मर्ज गरी कन्फ्रेन्स बनाउनुहोस्।"</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"होल्ड गर्न नमिल्ने कल चलिरहेको हुनाले नयाँ कल गर्न सकिँदैन। नयाँ कल गर्नुअघि यो कल डिस्कनेक्ट गर्नुहोस्।"</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"अर्को खातामार्फत कल चलिरहेका बेला यो MMI कोड प्रयोग गर्न मिल्दैन।"</string>
 </resources>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index e395ef1..bb6f2f7 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Audio streamen naar ander apparaat"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Ophangen"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Hiernaartoe schakelen"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Kan gesprek niet plaatsen omdat er al 2 actieve gesprekken zijn. Verbreek de verbinding in een van de gesprekken of voeg ze samen tot een conferencecall voordat je een nieuw gesprek plaatst."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Kan gesprek niet plaatsen omdat je het live gesprek niet in de wacht kunt zetten. Verbreek de verbinding van het live gesprek voordat je een nieuw gesprek plaatst."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Deze MMI-code is niet beschikbaar voor gesprekken met meerdere accounts."</string>
 </resources>
diff --git a/res/values-or/strings.xml b/res/values-or/strings.xml
index 535583a..56a1e81 100644
--- a/res/values-or/strings.xml
+++ b/res/values-or/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"ଅନ୍ୟ ଡିଭାଇସରେ ଅଡିଓ ଷ୍ଟ୍ରିମ କରାଯାଉଛି"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"କଲ ସମାପ୍ତ କରନ୍ତୁ"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"ଏଠାରେ ସୁଇଚ କରନ୍ତୁ"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"ପୂର୍ବରୁ ଦୁଇଟି କଲ ଚାଲୁ ଥିବା ଯୋଗୁଁ ଆଉ ଏକ କଲ କରାଯାଇପାରିବ ନାହିଁ। ଏକ ନୂଆ କଲ କରିବା ପୂର୍ବରୁ ଗୋଟିଏ କଲକୁ ଡିସକନେଜ୍ଟ କରନ୍ତୁ କିମ୍ବା ସେଗୁଡ଼ିକୁ ଏକ କନଫରେନ୍ସ କଲରେ ମର୍ଜ କରନ୍ତୁ।"</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"ହୋଲ୍ଡ କରିହେଉନଥିବା ଏକ କଲ ଚାଲୁ ଥିବା ଯୋଗୁଁ ଆଉ ଏକ କଲ କରାଯାଇପାରିବ ନାହିଁ। ଏକ ନୂଆ କଲ କରିବା ପୂର୍ବରୁ କଲକୁ ଡିସକନେକ୍ଟ କରନ୍ତୁ।"</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"ଏକାଧିକ ଆକାଉଣ୍ଟରେ କଲଗୁଡ଼ିକ ପାଇଁ ଏହି MMI କୋଡ ଉପଲବ୍ଧ ନାହିଁ।"</string>
 </resources>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 96ee0e8..1a42d0b 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"ਆਡੀਓ ਨੂੰ ਕਿਸੇ ਹੋਰ ਡੀਵਾਈਸ \'ਤੇ ਸਟ੍ਰੀਮ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"ਕਾਲ ਸਮਾਪਤ ਕਰੋ"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"ਇੱਥੇ ਸਵਿੱਚ ਕਰੋ"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"ਕਾਲ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ ਕਿਉਂਕਿ ਪਹਿਲਾਂ ਤੋਂ ਦੋ ਕਾਲਾਂ ਚੱਲ ਰਹੀਆਂ ਹਨ। ਨਵੀਂ ਕਾਲ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਇੱਕ ਕਾਲ ਨੂੰ ਡਿਸਕਨੈਕਟ ਕਰੋ ਜਾਂ ਦੋਨੋਂ ਕਾਲਾਂ ਨੂੰ ਮਿਲਾ ਕੇ ਕਾਨਫਰੰਸ ਕਾਲ ਵਿੱਚ ਬਦਲੋ।"</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"ਕਾਲ ਨਹੀਂ ਕੀਤੀ ਜਾ ਸਕਦੀ ਕਿਉਂਕਿ ਪਹਿਲਾਂ ਤੋਂ ਇੱਕ ਕਾਲ ਚੱਲ ਰਹੀ ਹੈ, ਜਿਸਨੂੰ ਹੋਲਡ \'ਤੇ ਨਹੀਂ ਰੱਖਿਆ ਜਾ ਸਕਦਾ। ਨਵੀਂ ਕਾਲ ਕਰਨ ਤੋਂ ਪਹਿਲਾਂ ਕਾਲ ਨੂੰ ਡਿਸਕਨੈਕਟ ਕਰੋ।"</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"ਇਹ MMI ਕੋਡ ਇੱਕ ਤੋਂ ਵੱਧ ਖਾਤਿਆਂ ਵਿੱਚ ਕਾਲਾਂ ਲਈ ਉਪਲਬਧ ਨਹੀਂ ਹੈ।"</string>
 </resources>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 23776f5..18e5309 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Strumieniowanie dźwięku na inne urządzenie"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Rozłącz"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Przełącz tutaj"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Nie można nawiązać połączenia, ponieważ trwają już 2 inne połączenia. Aby nawiązać nowe połączenie, zakończ jedno z nich lub scal je w połączenie konferencyjne."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Nie można nawiązać połączenia, ponieważ trwa połączenie, którego nie można wstrzymać. Aby nawiązać nowe połączenie, zakończ to połączenie."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Ten kod MMI nie jest dostępny w przypadku połączeń na więcej niż 1 koncie."</string>
 </resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 122615a..1eb70c3 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -134,4 +134,8 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"A fazer stream de áudio para outro dispositivo"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Desligar"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Mudar aqui"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Não pode fazer uma chamada porque já estão 2 chamadas em curso. Desligue uma delas ou una-as numa conferência antes de fazer uma nova chamada."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Não pode fazer uma chamada porque tem uma chamada que não pode ser colocada em espera. Termine essa chamada antes de fazer uma nova chamada."</string>
+    <!-- no translation found for callFailed_reject_mmi (5219280796733595167) -->
+    <skip />
 </resources>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index e302ea6..db74a2d 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Fazendo streaming de áudio para outro dispositivo"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Desligar"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Mudar para este dispositivo"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Não é possível ligar porque já há duas chamadas em andamento. Encerre uma delas ou mescle-as em uma conferência antes de fazer outra."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Não é possível ligar porque há uma chamada que não pode ficar em espera. Encerre essa ligação antes de fazer outra."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Este código MMI não está disponível para chamadas em várias contas."</string>
 </resources>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index fe5ad93..18499dc 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Streaming audio pe alt dispozitiv"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Încheie apelul"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Treci la alt cont aici"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Nu se poate iniția un apel când există deja două apeluri în desfășurare. Deconectează unul dintre ele sau îmbină-le într-o conferință înainte de a iniția un apel nou."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Nu se poate iniția un apel când există un apel care nu poate fi pus în așteptare. Închide apelul înainte de a iniția un apel nou."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Acest cod MMI nu este disponibil pentru apelurile din mai multe conturi."</string>
 </resources>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index cc69d40..36867f1 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Потоковая передача аудио на другое устройство"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Завершить"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Переключиться"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Невозможно позвонить, поскольку ещё не завершены два текущих вызова. Сбросьте один из вызовов или объедините их в конференцию."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Невозможно позвонить, поскольку нельзя поставить текущий вызов на удержание. Сбросьте вызов, чтобы начать новый."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Этот код MMI недоступен для вызовов с использованием нескольких аккаунтов."</string>
 </resources>
diff --git a/res/values-si/strings.xml b/res/values-si/strings.xml
index 2ea058f..9f0c406 100644
--- a/res/values-si/strings.xml
+++ b/res/values-si/strings.xml
@@ -134,4 +134,8 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"වෙනත් උපාංගයකට ශ්‍රව්‍ය ප්‍රවාහ කිරීම"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"විසන්ධි කරන්න"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"මෙතැනට මාරු වෙන්න"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"දැනටමත් ඇමතුම් දෙකක් කෙරෙමින් පවතින නිසා ඇමතුමක් ගැනීමට නොහැක. නව ඇමතුමක් ගැනීමට පෙරාතුව ඇමතුම්වලින් එකක් විසන්ධි කරන්න නැතහොත් ඒවා සම්මන්ත්‍රණයකට ඒකාබද්ධ කරන්න."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"රඳවා ගත නොහැකි ඇමතුමක් ඇති බැවින් ඇමතුමක් ලබා ගත නොහැක. නව ඇමතුමක් ලබා ගැනීමට පෙර ඇමතුම විසන්ධි කරන්න."</string>
+    <!-- no translation found for callFailed_reject_mmi (5219280796733595167) -->
+    <skip />
 </resources>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index fc7108a..af712ef 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Streamovanie zvuku do iného zariadenia"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Zložiť"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Prepnúť sem"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Hovor sa nedá uskutočniť, pretože už prebiehajú dva hovory. Odpojte jeden hovor alebo ich zlúčte do konferencie a až potom uskutočnite nový hovor."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Hovor sa nedá uskutočniť, pretože prebieha hovor, ktorý sa nedá podržať. Pred uskutočnením nového hovoru najprv ukončite ten prebiehajúci."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Tento kód MMI nie je k dispozícii pre hovory v rámci viacerých účtov."</string>
 </resources>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 7ee0b0b..ab9c227 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Pretočno predvajanje zvoka v drugo napravo"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Prekini klic"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Preklopi sem"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Klica ni mogoče opraviti, ker potekata že dva klica. Preden začnete nov klic, prekinite enega od klicev ali ju združite v konferenčni klic."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Klica ni mogoče opraviti, ker že imate klic, ki ga ni mogoče zadržati. Preden opravite nov klic, prekinite omenjeni klic."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Ta koda MMI ni na voljo za klice v več računih."</string>
 </resources>
diff --git a/res/values-sq/strings.xml b/res/values-sq/strings.xml
index 7d8045a..5eb20ca 100644
--- a/res/values-sq/strings.xml
+++ b/res/values-sq/strings.xml
@@ -134,4 +134,8 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Po transmetohet audioja te një pajisje tjetër"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Mbyll"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Ndërro këtu"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Nuk mund të kryhet një telefonatë sepse janë tashmë dy telefonata në vazhdim. Shkëput një nga telefonatat ose shkriji ato në një konferencë para se të kryesh një telefonatë të re."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Nuk mund të kryhet një telefonatë pasi është në telefonatë që nuk mund të vendoset në pritje. Shkëput telefonatën para se të kryesh një telefonatë të re."</string>
+    <!-- no translation found for callFailed_reject_mmi (5219280796733595167) -->
+    <skip />
 </resources>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 148cb14..ca1251e 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Звук се стримује на други уређај"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Прекини везу"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Пребаци овде"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Не можете да упутите позив јер су два позива већ у току. Прекините један од њих или их обједините у конференцију да бисте упутили нови позив."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Не можете да упутите позив јер је у току позив који не може да се стави на чекање. Прекините тај позив пре упућивања новог позива."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Овај MMI кôд није доступан за позиве на више налога."</string>
 </resources>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index d4a930c..570bcda 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Streama ljud till en annan enhet"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Lägg på"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Koppla hit"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Det går inte att ringa eftersom det redan finns två pågående samtal. Koppla bort ett eller slå ihop dem till en konferens innan du ringer ett nytt samtal."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Det går inte att ringa eftersom det finns ett samtal som inte kan sättas i vänteläge. Koppla bort samtalet innan du ringer ett nytt samtal."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Den här MMI-koden är inte tillgänglig för samtal på flera konton."</string>
 </resources>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index ac0518d..da0b0c9 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Inatiririsha sauti kwenye kifaa kingine"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Kata simu"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Badili hapa"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Imeshindwa kupiga simu kwa sababu tayari kuna simu mbili zinazoendelea. Kata mojawapo ya simu hizo au uziunganishe ili ziwe simu ya mkutano kabla ya kupiga simu mpya."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Imeshindwa kupiga simu kwa sababu kuna simu isiyoweza kusubirishwa. Kata simu kabla ya kupiga simu mpya."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Msimbo huu wa MMI haupatikani katika simu kwenye akaunti nyingi."</string>
 </resources>
diff --git a/res/values-ta/strings.xml b/res/values-ta/strings.xml
index 57c70f4..7447dcd 100644
--- a/res/values-ta/strings.xml
+++ b/res/values-ta/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"வேறு சாதனத்திற்கு ஆடியோவை ஸ்ட்ரீம் செய்கிறது"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"அழைப்பைத் துண்டி"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"இங்கே மாற்று"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"ஏற்கெனவே இரண்டு அழைப்புகள் செயலில் இருப்பதால் தற்போது புதிய அழைப்பை மேற்கொள்ள முடியாது. செயலில் உள்ள அழைப்புகளில் ஏதேனும் ஒன்றைத் துண்டித்தோ அவற்றை இணைத்து குழு அழைப்பாக மாற்றியோ புதிய அழைப்பை மேற்கொள்ளவும்."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"ஹோல்டு செய்ய முடியாத ஓர் அழைப்பு ஏற்கெனவே செயலில் இருப்பதால் அழைப்பை மேற்கொள்ள முடியவில்லை. செயலில் உள்ள அழைப்பைத் துண்டித்து புதிய அழைப்பை மேற்கொள்ளவும்."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"பல கணக்குகளில் மேற்கொள்ளப்படும் அழைப்புகளுக்கு இந்த MMI குறியீட்டைப் பயன்படுத்த முடியாது."</string>
 </resources>
diff --git a/res/values-te/strings.xml b/res/values-te/strings.xml
index 22f4b8a..3a37817 100644
--- a/res/values-te/strings.xml
+++ b/res/values-te/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"ఆడియోను ఇతర పరికరానికి స్ట్రీమింగ్ చేయండి"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"ముగించండి"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"ఇక్కడకు స్విచ్ అవ్వండి"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"ఇప్పటికే రెండు కాల్స్ జరుగుతున్నందున కాల్ చేయడం సాధ్యపడదు. ఆ కాల్స్‌లో ఒకదానిని డిస్‌కనెక్ట్ చేయండి లేదా అవి రెండింటినీ కలిపి ఒక కాన్ఫరెన్స్ కాల్‌గా మార్చి, తర్వాత కొత్త కాల్ చేయండి."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"హోల్డ్‌లో పెట్టడం సాధ్యం కాని కాల్ జరుగుతున్నందున కాల్ చేయడం సాధ్యం కాదు. కొత్త కాల్ చేయడానికంటే ముందుగా ప్రస్తుత కాల్‌ను డిస్‌కనెక్ట్ చేయండి."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"ఈ MMI కోడ్ పలు ఖాతాలలో కాల్స్ కోసం అందుబాటులో లేదు."</string>
 </resources>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index e3a20b1..692778a 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"กำลังสตรีมเสียงไปยังอุปกรณ์อื่นๆ"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"วางสาย"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"เปลี่ยนที่นี่"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"โทรออกไม่ได้เนื่องจากมีการโทร 2 สายที่กำลังดำเนินอยู่ โปรดยกเลิกการเชื่อมต่อสายใดสายหนึ่งหรือรวมเป็นการประชุมสายก่อนโทรออกใหม่"</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"โทรออกไม่ได้เนื่องจากมีการโทรที่ไม่สามารถพักสายได้ โปรดยกเลิกการเชื่อมต่อสายดังกล่าวก่อนโทรออกใหม่"</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"รหัส MMI นี้ใช้ไม่ได้กับการโทรผ่านหลายบัญชี"</string>
 </resources>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 001a19a..4e93bd4 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Naka-stream ang audio sa ibang device"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Mag-hang up"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Lumipat dito"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Hindi puwedeng tumawag dahil mayroon nang dalawang tawag na kasalukuyang nagaganap. Idiskonekta ang isa sa mga tawag o i-merge ang mga ito sa isang conference bago gumawa ng bagong pagtawag."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Hindi puwedeng tumawag dahil may tawag na hindi puwedeng i-hold. Idiskonekta ang tawag bago gumawa ng bagong pagtawag."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Hindi available ang MMI code na ito para sa mga tawag sa magkakaibang account."</string>
 </resources>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 1924d92..898507d 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Ses başka bir cihaza aktarılıyor"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Görüşmeyi bitir"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Buraya dön"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Devam eden iki arama olduğu için arama yapılamıyor. Yeni bir arama yapmadan önce aramalardan birini sonlandırın veya iki aramayı bir konferans aramasında birleştirin."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Bekletilemeyen bir arama devam ettiğinden arama yapılamıyor. Yeni bir arama yapmadan önce mevcut aramayı sonlandırın."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Bu MMI kodu, birden fazla hesaptan arama yapılırken kullanılamaz."</string>
 </resources>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index 2d4f5bc..f968147 100644
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Аудіо транслюється на інший пристрій"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Завершити"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Перевести сюди"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Неможливо зателефонувати, оскільки тривають уже два виклики. Припиніть один із них або об’єднайте їх у конференцію, перш ніж здійснити новий."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Неможливо зателефонувати, оскільки поточний виклик не можна поставити на утримання. Припиніть виклик, перш ніж здійснити новий."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Цей код MMI недоступний для дзвінків із використанням кількох облікових записів."</string>
 </resources>
diff --git a/res/values-ur/strings.xml b/res/values-ur/strings.xml
index b09f244..45e1291 100644
--- a/res/values-ur/strings.xml
+++ b/res/values-ur/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"دوسرے آلے پر آڈیو کی سلسلہ بندی کی جا رہی ہے"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"منقطع کریں"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"یہاں سوئچ کریں"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"پہلے سے دو کالز کے پیش رفت میں ہونے کی وجہ سے کال نہیں کی جا سکتی۔ نئی کال کرنے کیلئے پہلے ان میں سے ایک کو غیر منسلک کریں یا انہیں کانفرنس میں ضم کریں۔"</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"کال نہیں کر سکتے کیونکہ پہلے سے جاری کال کو ہولڈ نہیں کیا جا سکتا۔ نئی کال کرنے سے پہلے موجودہ کال کو غیر منسلک کریں۔"</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"‏یہ MMI کوڈ متعدد اکاؤنٹس پر کالز کے لیے دستیاب نہیں ہے۔"</string>
 </resources>
diff --git a/res/values-uz/strings.xml b/res/values-uz/strings.xml
index ff04903..6fe2d12 100644
--- a/res/values-uz/strings.xml
+++ b/res/values-uz/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Audio translatsiyani boshqa qurilmaga olish"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Tugatish"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Shu yerga olish"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Telefon qilish imkonsiz, chunki ayni paytda ikkita chaqiruv davom etmoqda. Telefon qilish uchun chaqiruvlardan birini yakunlang yoki ularni konferens-aloqaga birlashtiring."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Chaqirish imkonsiz, chunki joriy chaqiruv pauza qilinmaydi. Yangisini boshlash uchun chaqiruvni bekor qiling."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Bu MMI kodi bir nechta hisobdagi chaqiruvlarda ishlamaydi."</string>
 </resources>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index 142026c..229b50c 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Đang truyền trực tuyến âm thanh tới thiết bị khác"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Kết thúc"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Chuyển qua thiết bị này"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Không thể gọi điện vì có 2 cuộc gọi đang diễn ra. Hãy ngắt kết nối 1 trong 2 cuộc gọi hoặc gộp thành 1 cuộc gọi kiểu hội nghị truyền hình trước khi thực hiện cuộc gọi mới."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Không thể thực hiện cuộc gọi vì có một cuộc gọi không thể tạm ngưng. Hãy ngắt kết nối với cuộc gọi đó trước khi thực hiện cuộc gọi mới."</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"Mã MMI này không dùng được cho các cuộc gọi trên nhiều tài khoản."</string>
 </resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 7cb8a7a..4e9a04b 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"将音频流式传输到其他设备"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"挂断"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"在此处切换"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"由于已有两个正在进行的通话,因此无法拨打电话。请先断开其中一个通话的连接或将两个通话合并到同一个会议中,然后才能拨打新电话。"</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"由于有无法暂停的通话,因此不能拨打电话。请先断开通话,然后再拨打新电话。"</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"此 MMI 码无法用于跨多个账号的通话。"</string>
 </resources>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index 213255a..79fc475 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"正在串流音訊至其他裝置"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"結束通話"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"在這裡切換"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"目前已有兩個通話正在進行,因此無法撥打電話。請先結束其中一個通話,或將兩個通話合併為一個會議,然後再撥打電話。"</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"目前有一個無法保留的通話,因此無法撥打電話。請先結束通話,然後再撥打電話。"</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"在透過多個帳戶進行通話時,無法使用 MMI 碼。"</string>
 </resources>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index 287f627..07cd2da 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -134,4 +134,7 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"正在將音訊串流到其他裝置"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"掛斷"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"切換到這部裝置"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"目前有兩個進行中的通話,因此無法撥號。如要撥打電話,請掛斷其中一個通話,或將通話合併成會議。"</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"目前有無法保留的通話,因此無法撥號。請先掛斷通話,再撥打電話。"</string>
+    <string name="callFailed_reject_mmi" msgid="5219280796733595167">"這個 MMI 代碼無法用於透過多個帳戶進行通話。"</string>
 </resources>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 8d0437d..2849db8 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -134,4 +134,8 @@
     <string name="call_streaming_notification_body" msgid="502216105683378263">"Sakaza umsindo kwenye idivayisi"</string>
     <string name="call_streaming_notification_action_hang_up" msgid="7017663335289063827">"Beka phansi"</string>
     <string name="call_streaming_notification_action_switch_here" msgid="3524180754186221228">"Shintsha lapha"</string>
+    <string name="callFailed_too_many_calls" msgid="4249997210954876420">"Awukwazi ukubeka ikholi njengoba kunamakholi amabili aqhubekayo kakade. Nqamula eyodwa yamakholi noma wahlanganisele enkofeni ngaphambi kokubeka ikholi entsha."</string>
+    <string name="callFailed_unholdable_call" msgid="7580834131274566524">"Ayikwazi ukwenza ikholi njengoba kukhona ikholi engabanjwa. Nqamula ikholi ngaphambi kokwenza ikholi entsha."</string>
+    <!-- no translation found for callFailed_reject_mmi (5219280796733595167) -->
+    <skip />
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 195bf97..9d9f5e1 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -423,4 +423,10 @@
     <!-- In-call screen: error message shown when the user attempts to place a call, but the live
          call cannot be held. -->
     <string name="callFailed_unholdable_call">Cannot place a call as there is an unholdable call. Disconnect the call prior to placing a new call.</string>
+    <!-- In-call screen: error message shown when the user has attempted to place a new outgoing
+         call while there is already a call in ringing state. -->
+    <string name="callFailed_already_ringing">Cannot place a call as there is an unanswered incoming call. Answer or reject the incoming call prior to placing a new call.</string>
+    <!-- In-call screen: error message shown when the user attempts to dial an MMI code, but there
+         is an ongoing call on a different phone account. -->
+    <string name="callFailed_reject_mmi">This MMI code is not available for calls across multiple accounts.</string>
 </resources>
diff --git a/src/com/android/server/telecom/AsyncRingtonePlayer.java b/src/com/android/server/telecom/AsyncRingtonePlayer.java
index 3b5e342..7cb05cd 100644
--- a/src/com/android/server/telecom/AsyncRingtonePlayer.java
+++ b/src/com/android/server/telecom/AsyncRingtonePlayer.java
@@ -23,6 +23,7 @@
 import android.net.Uri;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.Looper;
 import android.os.Message;
 import android.telecom.Log;
 import android.telecom.Logging.Session;
@@ -184,6 +185,13 @@
         }
     }
 
+    public @NonNull Looper getLooper() {
+        if (mHandler == null) {
+            mHandler = getNewHandler();
+        }
+        return mHandler.getLooper();
+    }
+
     /**
      * Creates a new ringtone Handler running in its own thread.
      */
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 75d8d7d..3b3a35a 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -3216,7 +3216,18 @@
             } else if (mConnectionService != null) {
                 if (mFlags.transactionalCsVerifier() || mFlags.enableCallSequencing()) {
                     holdFutureHandler = awaitCallStateChangeAndMaybeDisconnectCall(isSelfManaged(),
-                            "hold", CallState.ON_HOLD);
+                            "hold", CallState.ON_HOLD, CallState.DISCONNECTED).thenCompose(
+                                    (result) -> {
+                                        // Explicitly handle self-managed hold failures where we
+                                        // explicitly disconnect the call and treat it as a
+                                        // completed transaction.
+                                        if (!result && isSelfManaged()) {
+                                            Log.i(this, "hold: Completing transaction "
+                                                    + "after disconnecting held call.");
+                                            return CompletableFuture.completedFuture(true);
+                                        }
+                                        return CompletableFuture.completedFuture(result);
+                                    });;
                 }
                 mConnectionService.hold(this);
                 return holdFutureHandler;
diff --git a/src/com/android/server/telecom/CallAudioRouteController.java b/src/com/android/server/telecom/CallAudioRouteController.java
index 57e64bb..29e67bb 100644
--- a/src/com/android/server/telecom/CallAudioRouteController.java
+++ b/src/com/android/server/telecom/CallAudioRouteController.java
@@ -65,6 +65,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
@@ -120,6 +121,8 @@
     private boolean mAvailableRoutesUpdated;
     private final Object mLock = new Object();
     private final TelecomSystem.SyncRoot mTelecomLock;
+    private CountDownLatch mAudioOperationsCompleteLatch;
+    private CountDownLatch mAudioActiveCompleteLatch;
     private final BroadcastReceiver mSpeakerPhoneChangeReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -230,10 +233,12 @@
             @Override
             public void onCommunicationDeviceChanged(AudioDeviceInfo device) {
                 @AudioRoute.AudioRouteType int audioType = device != null
-                        ? DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.get(device.getType())
+                        ? DEVICE_INFO_TYPE_TO_AUDIO_ROUTE_TYPE.getOrDefault(
+                                device.getType(), TYPE_INVALID)
                         : TYPE_INVALID;
-                Log.i(this, "onCommunicationDeviceChanged: %d", audioType);
-                if (device != null && device.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
+                Log.i(this, "onCommunicationDeviceChanged: device (%s), audioType (%d)",
+                        device, audioType);
+                if (audioType == TYPE_SPEAKER) {
                     if (mCurrentRoute.getType() != TYPE_SPEAKER) {
                         sendMessageWithSessionInfo(SPEAKER_ON);
                     }
@@ -440,6 +445,12 @@
         } else {
             mCurrentRoute = DUMMY_ROUTE;
         }
+        // Audio ops will only ever be completed if there's a call placed and it gains
+        // ACTIVE/RINGING focus, hence why the initial value is 0.
+        mAudioOperationsCompleteLatch = new CountDownLatch(0);
+        // This latch will be count down when ACTIVE/RINGING focus is gained. This is determined
+        // when the routing goes active.
+        mAudioActiveCompleteLatch = new CountDownLatch(1);
         mIsActive = false;
         mCallAudioState = new CallAudioState(mIsMute, ROUTE_MAP.get(mCurrentRoute.getType()),
                 supportMask, null, new HashSet<>());
@@ -653,7 +664,16 @@
 
         // Route to expected state
         if (mCurrentRoute.equals(wiredHeadsetRoute)) {
-            routeTo(mIsActive, getBaseRoute(true, null));
+            // Preserve speaker routing if it was the last audio routing path when the wired headset
+            // disconnects. Ignore this special cased routing when the route isn't active
+            // (in other words, when we're not in a call).
+            AudioRoute route = mFeatureFlags.defaultSpeakerOnWiredHeadsetDisconnect()
+                    && mIsActive && mPendingAudioRoute.getOrigRoute() != null
+                    && mPendingAudioRoute.getOrigRoute().getType() == TYPE_SPEAKER
+                    && mSpeakerDockRoute != null
+                    && mSpeakerDockRoute.getType() == AudioRoute.TYPE_SPEAKER
+                    ? mSpeakerDockRoute : getBaseRoute(true, null);
+            routeTo(mIsActive, route);
         }
     }
 
@@ -1116,6 +1136,21 @@
             mIsPending = false;
             mPendingAudioRoute.clearPendingMessages();
             onCurrentRouteChanged();
+            if (mIsActive) {
+                // Reinitialize the audio ops complete latch since the routing went active. We
+                // should always expect operations to complete after this point.
+                if (mAudioOperationsCompleteLatch.getCount() == 0) {
+                    mAudioOperationsCompleteLatch = new CountDownLatch(1);
+                }
+                mAudioActiveCompleteLatch.countDown();
+            } else {
+                // Reinitialize the active routing latch when audio ops are complete so that it can
+                // once again be processed when a new call is placed/received.
+                if (mAudioActiveCompleteLatch.getCount() == 0) {
+                    mAudioActiveCompleteLatch = new CountDownLatch(1);
+                }
+                mAudioOperationsCompleteLatch.countDown();
+            }
             if (mFeatureFlags.telecomMetricsSupport()) {
                 mMetricsController.getAudioRouteStats().onRouteExit(mPendingAudioRoute, true);
             }
@@ -1648,4 +1683,12 @@
         sendMessageWithSessionInfo(SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE,
                 btAddressToExclude);
     }
+
+    public CountDownLatch getAudioOperationsCompleteLatch() {
+        return mAudioOperationsCompleteLatch;
+    }
+
+    public CountDownLatch getAudioActiveCompleteLatch() {
+        return mAudioActiveCompleteLatch;
+    }
 }
diff --git a/src/com/android/server/telecom/CallAudioWatchdog.java b/src/com/android/server/telecom/CallAudioWatchdog.java
index 53691cc..dcfc80f 100644
--- a/src/com/android/server/telecom/CallAudioWatchdog.java
+++ b/src/com/android/server/telecom/CallAudioWatchdog.java
@@ -37,6 +37,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.telecom.metrics.TelecomMetricsController;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -367,15 +368,18 @@
     // Local logs for tracking non-telecom calls.
     private final LocalLog mLocalLog = new LocalLog(30);
 
+    private final TelecomMetricsController mMetricsController;
+
     public CallAudioWatchdog(AudioManager audioManager,
             PhoneAccountRegistrarProxy phoneAccountRegistrarProxy, ClockProxy clockProxy,
-            Handler handler) {
+            Handler handler, TelecomMetricsController metricsController) {
         mPhoneAccountRegistrarProxy = phoneAccountRegistrarProxy;
         mClockProxy = clockProxy;
         mAudioManager = audioManager;
         mHandler = handler;
         mAudioManager.registerAudioPlaybackCallback(mWatchdogAudioPlayback, mHandler);
         mAudioManager.registerAudioRecordingCallback(mWatchdogAudioRecordCallack, mHandler);
+        mMetricsController = metricsController;
     }
 
     /**
@@ -395,7 +399,10 @@
 
     @Override
     public void onCallRemoved(Call call) {
-        // Nothing to do for call removal; sessions get cleaned up when their audio goes away.
+        // Only track for voip calls.
+        if (call.isSelfManaged() || call.isTransactionalCall()) {
+            maybeRemoveCall(call);
+        }
     }
 
     @VisibleForTesting
@@ -428,7 +435,7 @@
         }
         sessions.forEach(pw::println);
         pw.decreaseIndent();
-        pw.println("Non-Telecom Sessions:");
+        pw.println("Audio sessions Sessions:");
         pw.increaseIndent();
         mLocalLog.dump(pw);
         pw.decreaseIndent();
@@ -544,6 +551,28 @@
     }
 
     /**
+     * Given a telecom call, cleanup the session if there are no audio resources remaining for that
+     * session.
+     * @param call The call.
+     */
+    private void maybeRemoveCall(Call call) {
+        int uid = mPhoneAccountRegistrarProxy.getUidForPhoneAccountHandle(
+                call.getTargetPhoneAccount());
+        CommunicationSession session;
+        synchronized (mCommunicationSessionsLock) {
+            session = getSession(uid);
+            if (session == null) {
+                return;
+            }
+            if (!session.hasMediaResources()) {
+                mLocalLog.log(session.toString());
+                maybeLogMetrics(session);
+                mCommunicationSessions.remove(uid);
+            }
+        }
+    }
+
+    /**
      * Returns an existing session for a uid, or {@code null} if none exists.
      * @param uid the uid,
      * @return The session found, or {@code null}.
@@ -607,7 +636,8 @@
                         Collections.emptySet());
 
                 // Update the known sessions of this resource type in the CommunicationSession.
-                Set<Integer> trackedSessions = session.getAudioResourcesByType().get(bit);
+                Set<Integer> trackedSessions = putOrDefault(session.getAudioResourcesByType(), bit,
+                        new ArraySet<>());
                 trackedSessions.clear();
                 trackedSessions.addAll(sessionsForThisUid);
 
@@ -620,14 +650,10 @@
 
                 // If audio resources are no longer held for a uid, then we'll clean up its
                 // media session.
-                if (!session.hasMediaResources()) {
+                if (!session.hasMediaResources() && session.getTelecomCall() == null) {
                     Log.i(this, "cleanupAttributeForSessions: removing session %s", session);
-                    // Only log the audio session if it has no telecom call; we'll correlate to
-                    // a telecom call if one was present so the logs for a telecom call will be
-                    // in the calls dumpsys.
-                    if (session.getTelecomCall() == null) {
-                        mLocalLog.log(session.toString());
-                    }
+                    mLocalLog.log(session.toString());
+                    maybeLogMetrics(session);
                     iterator.remove();
                 }
             }
@@ -656,4 +682,18 @@
         map.put(key, theDefault);
         return theDefault;
     }
+
+    /**
+     * If this call has no associated Telecom {@link Call} and metrics are enabled, log this as a
+     * non-telecom call.
+     * @param session the session to log.
+     */
+    private void maybeLogMetrics(CommunicationSession session) {
+        if (mMetricsController != null && session.getTelecomCall() == null) {
+            mMetricsController.getCallStats().onNonTelecomCallEnd(
+                    session.isBitSet(SESSION_ATTR_HAS_PHONE_ACCOUNT),
+                    session.getUid(),
+                    mClockProxy.elapsedRealtime() - session.getSessionStartMillis());
+        }
+    }
 }
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index f6a1fd8..abcdd18 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -278,7 +278,7 @@
      * {@link #getNumCallsWithState(int, Call, PhoneAccountHandle, int...)} to indicate both managed
      * and self-managed calls should be included.
      */
-    private static final int CALL_FILTER_ALL = 3;
+    public static final int CALL_FILTER_ALL = 3;
 
     private static final String PERMISSION_PROCESS_PHONE_ACCOUNT_REGISTRATION =
             "android.permission.PROCESS_PHONE_ACCOUNT_REGISTRATION";
@@ -329,6 +329,10 @@
             UUID.fromString("0a86157c-50ca-11ee-be56-0242ac120002");
     public static final String TELEPHONY_HAS_DEFAULT_BUT_TELECOM_DOES_NOT_MSG =
             "Telephony has a default MO acct but Telecom prompted user for MO";
+    public static final UUID CANNOT_HOLD_CURRENT_ACTIVE_CALL_ERROR_UUID =
+            UUID.fromString("1b6a9b88-5049-4ffa-a52a-134d7c3a40e6");
+    public static final UUID FAILED_TO_SWITCH_FOCUS_ERROR_UUID =
+            UUID.fromString("a1b2c3d4-e5f6-7890-1234-567890abcdef");
 
     public static final int[] OUTGOING_CALL_STATES =
             {CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING,
@@ -375,6 +379,7 @@
                 Analytics.THIRD_PARTY_PHONE);
     }
 
+    private static final long WAIT_FOR_AUDIO_UPDATE_TIMEOUT = 4000L;
     /**
      * The main call repository. Keeps an instance of all live calls. New incoming and outgoing
      * calls are added to the map and removed when the calls move to the disconnected state.
@@ -486,6 +491,9 @@
     private final ConnectionServiceFocusManager mConnectionSvrFocusMgr;
     /* Handler tied to thread in which CallManager was initialized. */
     private final Handler mHandler = new Handler(Looper.getMainLooper());
+    private final HandlerThread mHandlerThread = new HandlerThread("telecomAudioCallbacks",
+            android.os.Process.THREAD_PRIORITY_BACKGROUND);
+    private final Handler mAudioCallbackHandler;
     private final EmergencyCallHelper mEmergencyCallHelper;
     private final RoleManagerAdapter mRoleManagerAdapter;
     private final VoipCallMonitor mVoipCallMonitor;
@@ -506,6 +514,7 @@
 
     private final IncomingCallFilterGraphProvider mIncomingCallFilterGraphProvider;
     private final CallAudioWatchdog mCallAudioWatchDog;
+    private final CallAudioRouteAdapter mCallAudioRouteAdapter;
 
     private final ConnectionServiceFocusManager.CallsManagerRequester mRequester =
             new ConnectionServiceFocusManager.CallsManagerRequester() {
@@ -653,6 +662,8 @@
         mEmergencyCallDiagnosticLogger = emergencyCallDiagnosticLogger;
         mIncomingCallFilterGraphProvider = incomingCallFilterGraphProvider;
         if (featureFlags.enableCallAudioWatchdog()) {
+            mHandlerThread.start();
+            mAudioCallbackHandler = new Handler(mHandlerThread.getLooper());
             mCallAudioWatchDog = new CallAudioWatchdog(
                     mContext.getSystemService(AudioManager.class),
                     new CallAudioWatchdog.PhoneAccountRegistrarProxy() {
@@ -673,19 +684,20 @@
                                 return -1;
                             }
                         }
-                    }, clockProxy, mHandler);
+                    }, clockProxy, mAudioCallbackHandler,
+                    featureFlags.telecomMetricsSupport() ? metricsController : null);
         } else {
+            mAudioCallbackHandler = null;
             mCallAudioWatchDog = null;
         }
 
         mDtmfLocalTonePlayer =
                 new DtmfLocalTonePlayer(new DtmfLocalTonePlayer.ToneGeneratorProxy());
-        CallAudioRouteAdapter callAudioRouteAdapter;
         // TODO: add another flag check when
         // bluetoothDeviceManager.getBluetoothHeadset().isScoManagedByAudio()
         // available and return true
         if (!featureFlags.useRefactoredAudioRouteSwitching()) {
-            callAudioRouteAdapter = callAudioRouteStateMachineFactory.create(
+            mCallAudioRouteAdapter = callAudioRouteStateMachineFactory.create(
                     context,
                     this,
                     bluetoothManager,
@@ -698,17 +710,17 @@
                     featureFlags
             );
         } else {
-            callAudioRouteAdapter = new CallAudioRouteController(context, this, audioServiceFactory,
-                    new AudioRoute.Factory(), wiredHeadsetManager, mBluetoothRouteManager,
-                    statusBarNotifier, featureFlags, metricsController);
+            mCallAudioRouteAdapter = new CallAudioRouteController(context, this,
+                    audioServiceFactory, new AudioRoute.Factory(), wiredHeadsetManager,
+                    mBluetoothRouteManager, statusBarNotifier, featureFlags, metricsController);
         }
-        callAudioRouteAdapter.initialize();
-        bluetoothStateReceiver.setCallAudioRouteAdapter(callAudioRouteAdapter);
-        bluetoothDeviceManager.setCallAudioRouteAdapter(callAudioRouteAdapter);
+        mCallAudioRouteAdapter.initialize();
+        bluetoothStateReceiver.setCallAudioRouteAdapter(mCallAudioRouteAdapter);
+        bluetoothDeviceManager.setCallAudioRouteAdapter(mCallAudioRouteAdapter);
 
         CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter =
                 new CallAudioRoutePeripheralAdapter(
-                        callAudioRouteAdapter,
+                        mCallAudioRouteAdapter,
                         bluetoothManager,
                         wiredHeadsetManager,
                         mDockManager,
@@ -744,7 +756,7 @@
             mCallRecordingTonePlayer = new CallRecordingTonePlayer(mContext, audioManager,
                     mTimeoutsAdapter, mLock);
         }
-        mCallAudioManager = new CallAudioManager(callAudioRouteAdapter,
+        mCallAudioManager = new CallAudioManager(mCallAudioRouteAdapter,
                 this, callAudioModeStateMachineFactory.create(systemStateHelper,
                 (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE),
                 featureFlags, communicationDeviceTracker),
@@ -771,7 +783,10 @@
         mCallStreamingNotification = callStreamingNotification;
         mFeatureFlags = featureFlags;
         if (mFeatureFlags.voipCallMonitorRefactor()) {
-            mVoipCallMonitor = new VoipCallMonitor(mContext, mLock);
+            mVoipCallMonitor = new VoipCallMonitor(
+                    mContext,
+                    new Handler(Looper.getMainLooper()),
+                    mLock);
             mVoipCallMonitorLegacy = null;
         } else {
             mVoipCallMonitor = null;
@@ -782,9 +797,10 @@
         mBlockedNumbersManager = mFeatureFlags.telecomMainlineBlockedNumbersManager()
                 ? mContext.getSystemService(BlockedNumbersManager.class)
                 : null;
-        mCallSequencingAdapter = new CallsManagerCallSequencingAdapter(this,
-                new CallSequencingController(this, mContext,
-                        mFeatureFlags), mFeatureFlags);
+        mCallSequencingAdapter = new CallsManagerCallSequencingAdapter(this, mContext,
+                new CallSequencingController(this, mContext, mClockProxy,
+                        mAnomalyReporter, mTimeoutsAdapter, mMetricsController,
+                        mFeatureFlags), mCallAudioManager, mFeatureFlags);
 
         if (mFeatureFlags.useImprovedListenerOrder()) {
             mListeners.add(mInCallController);
@@ -2172,7 +2188,15 @@
                 potentialPhoneAccounts -> {
                     Log.i(CallsManager.this, "make room for outgoing call stage");
                     if (mMmiUtils.isPotentialInCallMMICode(handle) && !isSelfManaged) {
-                        return CompletableFuture.completedFuture(true);
+                        boolean shouldAllowMmiCode = mCallSequencingAdapter
+                                .shouldAllowMmiCode(finalCall);
+                        if (shouldAllowMmiCode) {
+                            return CompletableFuture.completedFuture(true);
+                        } else {
+                            Log.i(this, "Rejecting the MMI code because there is an "
+                                    + "ongoing call on a different phone account.");
+                            return CompletableFuture.completedFuture(false);
+                        }
                     }
                     // If a call is being reused, then it has already passed the
                     // makeRoomForOutgoingCall check once and will fail the second time due to the
@@ -3218,9 +3242,9 @@
      * CS: Hold any existing calls, request focus, and then set the call state to answered state.
      * <p>
      * T: Call TransactionalServiceWrapper, which then generates transactions to hold calls
-     * {@link #transactionHoldPotentialActiveCallForNewCall} and then move the active call focus
-     * {@link #requestNewCallFocusAndVerify} and notify the remote VOIP app of the call state
-     * moving to active.
+     * {@link CallsManagerCallSequencingAdapter#transactionHoldPotentialActiveCallForNewCall} and
+     * then move the active call focus {@link #requestNewCallFocusAndVerify} and notify the remote
+     * VOIP app of the call state moving to active.
      * <p>
      * Note: This is only used when {@link FeatureFlags#enableCallSequencing()} is false.
      */
@@ -4071,63 +4095,10 @@
     }
 
     /**
-     * attempt to hold or swap the current active call in favor of a new call request. The
-     * OutcomeReceiver will return onResult if the current active call is held or disconnected.
-     * Otherwise, the OutcomeReceiver will fail.
+     * Attempt to hold or swap the current active call in favor of a new call request. The old code
+     * path where {@link FeatureFlags#transactionalHoldDisconnectsUnholdable} is enabled but
+     * {@link FeatureFlags#enableCallSequencing()} is disabled.
      */
-    public void transactionHoldPotentialActiveCallForNewCall(Call newCall,
-            boolean isCallControlRequest, OutcomeReceiver<Boolean, CallException> callback) {
-        String mTag = "transactionHoldPotentialActiveCallForNewCall: ";
-        Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
-        Log.i(this, mTag + "newCall=[%s], activeCall=[%s]", newCall, activeCall);
-
-        if (activeCall == null || activeCall == newCall) {
-            Log.i(this, mTag + "no need to hold activeCall");
-            callback.onResult(true);
-            return;
-        }
-
-        if (mFeatureFlags.transactionalHoldDisconnectsUnholdable()) {
-            // prevent bad actors from disconnecting the activeCall. Instead, clients will need to
-            // notify the user that they need to disconnect the ongoing call before making the
-            // new call ACTIVE.
-            if (isCallControlRequest && !canHoldOrSwapActiveCall(activeCall, newCall)) {
-                Log.i(this, mTag + "CallControlRequest exit");
-                callback.onError(new CallException("activeCall is NOT holdable or swappable, please"
-                        + " request the user disconnect the call.",
-                        CallException.CODE_CANNOT_HOLD_CURRENT_ACTIVE_CALL));
-                return;
-            }
-
-            mCallSequencingAdapter.transactionHoldPotentialActiveCallForNewCall(newCall,
-                    activeCall, callback);
-        } else {
-            // 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);
-        }
-    }
-
     public void transactionHoldPotentialActiveCallForNewCallOld(Call newCall,
             Call activeCall, OutcomeReceiver<Boolean, CallException> callback) {
         if (holdActiveCallForNewCall(newCall)) {
@@ -4141,16 +4112,57 @@
             if (activeCall.isLocallyDisconnecting()) {
                 callback.onResult(true);
             } else {
-                Log.i(this, "transactionHoldPotentialActiveCallForNewCallOld: active call could "
-                        + "not be held or disconnected");
+                String msg = "active call could not be held or disconnected";
+                Log.i(this, "transactionHoldPotentialActiveCallForNewCallOld: " + msg);
                 callback.onError(
-                        new CallException("activeCall could not be held or disconnected",
+                        new CallException(msg,
                                 CallException.CODE_CANNOT_HOLD_CURRENT_ACTIVE_CALL));
+                if (mFeatureFlags.enableCallExceptionAnomReports()) {
+                    mAnomalyReporter.reportAnomaly(CANNOT_HOLD_CURRENT_ACTIVE_CALL_ERROR_UUID, msg);
+                }
             }
         }
     }
 
-    private boolean canHoldOrSwapActiveCall(Call activeCall, Call newCall) {
+    /**
+     * The transactional unflagged (original) code path to hold or swap the active call in favor of
+     * a new call request. Refer to
+     * {@link CallsManagerCallSequencingAdapter#transactionHoldPotentialActiveCallForNewCall}.
+     */
+    public void transactionHoldPotentialActiveCallForNewCallUnflagged(Call activeCall, Call newCall,
+            OutcomeReceiver<Boolean, CallException> callback) {
+        // before attempting CallsManager#holdActiveCallForNewCall(Call), check if it'll fail
+        // early
+        if (!canHold(activeCall) &&
+                !(supportsHold(activeCall) && areFromSameSource(activeCall, newCall))) {
+            String msg = "call does not support hold";
+            Log.i(this, "transactionHoldPotentialActiveCallForNewCall: " + msg);
+            callback.onError(new CallException(msg,
+                    CallException.CODE_CANNOT_HOLD_CURRENT_ACTIVE_CALL));
+            if (mFeatureFlags.enableCallExceptionAnomReports()) {
+                mAnomalyReporter.reportAnomaly(CANNOT_HOLD_CURRENT_ACTIVE_CALL_ERROR_UUID, msg);
+            }
+            return;
+        }
+
+        // attempt to hold the active call
+        if (!holdActiveCallForNewCall(newCall)) {
+            String msg = "cannot hold active call failed";
+            Log.i(this, "transactionHoldPotentialActiveCallForNewCall: " + msg);
+            callback.onError(new CallException(msg,
+                    CallException.CODE_CANNOT_HOLD_CURRENT_ACTIVE_CALL));
+            if (mFeatureFlags.enableCallExceptionAnomReports()) {
+                mAnomalyReporter.reportAnomaly(CANNOT_HOLD_CURRENT_ACTIVE_CALL_ERROR_UUID, msg);
+            }
+            return;
+        }
+
+        // officially mark the activeCall as held
+        markCallAsOnHold(activeCall);
+        callback.onResult(true);
+    }
+
+    public boolean canHoldOrSwapActiveCall(Call activeCall, Call newCall) {
         return canHold(activeCall) || sameSourceHoldCase(activeCall, newCall);
     }
 
@@ -4370,54 +4382,7 @@
         removeCall(call);
         boolean isLocallyDisconnecting = mLocallyDisconnectingCalls.contains(call);
         mLocallyDisconnectingCalls.remove(call);
-        maybeMoveHeldCallToForeground(call, isLocallyDisconnecting);
-    }
-
-    /**
-     * Move the held call to foreground in the event that there is a held call and the disconnected
-     * call was disconnected locally or the held call has no way to auto-unhold because it does not
-     * support hold capability.
-     *
-     * Note: If {@link FeatureFlags#enableCallSequencing()} is enabled, we will verify that the
-     * transaction to unhold the call succeeded or failed.
-     */
-    public void maybeMoveHeldCallToForeground(Call removedCall, boolean isLocallyDisconnecting) {
-        CompletableFuture<Boolean> unholdForegroundCallFuture = null;
-        Call foregroundCall = mCallAudioManager.getPossiblyHeldForegroundCall();
-        if (isLocallyDisconnecting) {
-            boolean isDisconnectingChildCall = removedCall.isDisconnectingChildCall();
-            Log.v(this, "maybeMoveHeldCallToForeground: isDisconnectingChildCall = "
-                    + isDisconnectingChildCall + "call -> %s", removedCall);
-            // 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, removedCall)) {
-
-                unholdForegroundCallFuture = foregroundCall.unhold();
-            }
-        } 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, "maybeMoveHeldCallToForeground: Auto-unholding held foreground call (call "
-                    + "doesn't support hold)");
-            unholdForegroundCallFuture = foregroundCall.unhold();
-        }
-
-        if (mFeatureFlags.enableCallSequencing() && unholdForegroundCallFuture != null) {
-            mCallSequencingAdapter.logFutureResultTransaction(unholdForegroundCallFuture,
-                    "maybeMoveHeldCallToForeground", "CM.mMHCTF",
-                    "Successfully unheld the foreground call.",
-                    "Failed to unhold the foreground call.");
-        }
+        mCallSequencingAdapter.maybeMoveHeldCallToForeground(call, isLocallyDisconnecting);
     }
 
     /**
@@ -4496,6 +4461,11 @@
                 CallState.SIMULATED_RINGING, CallState.RINGING, CallState.ANSWERED) != null;
     }
 
+    public boolean hasManagedRingingOrSimulatedRingingCall() {
+        return getFirstCallWithState(null /* callToSkip */, true /* skipSelfManaged */,
+                CallState.SIMULATED_RINGING, CallState.RINGING, CallState.ANSWERED) != null;
+    }
+
     @VisibleForTesting
     public boolean onMediaButton(int type) {
         if (hasAnyCalls()) {
@@ -4639,11 +4609,11 @@
 
     @VisibleForTesting
     public Call getFirstCallWithState(int... states) {
-        return getFirstCallWithState(null, states);
+        return getFirstCallWithState(null, false /* skipSelfManaged */, states);
     }
 
     public Call getFirstCallWithLiveState() {
-        return getFirstCallWithState(null, LIVE_CALL_STATES);
+        return getFirstCallWithState(null, false /* skipSelfManaged */, LIVE_CALL_STATES);
     }
 
     @VisibleForTesting
@@ -4668,7 +4638,7 @@
      *
      * @param callToSkip Call that this method should skip while searching
      */
-    Call getFirstCallWithState(Call callToSkip, int... states) {
+    Call getFirstCallWithState(Call callToSkip, boolean skipSelfManaged, int... states) {
         for (int currentState : states) {
             // check the foreground first
             Call foregroundCall = getForegroundCall();
@@ -4690,6 +4660,10 @@
                     continue;
                 }
 
+                if (skipSelfManaged && call.isSelfManaged()) {
+                    continue;
+                }
+
                 if (currentState == call.getState()) {
                     return call;
                 }
@@ -5139,14 +5113,40 @@
      *                   ({@link #CALL_FILTER_ALL}).
      * @param excludeCall Where {@code non-null}, this call is excluded from the count.
      * @param phoneAccountHandle Where {@code non-null}, calls for this {@link PhoneAccountHandle}
-     *                           are excluded from the count.
+     *                           are included in 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,
                                     PhoneAccountHandle phoneAccountHandle, int... states) {
+        Stream<Call> callsStream = getCallsWithState(callFilter, excludeCall, states);
 
+        // 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()));
+        }
+
+        return (int) callsStream.count();
+    }
+
+    @VisibleForTesting
+    public int getNumCallsWithStateWithoutHandle(final int callFilter, Call excludeCall,
+            PhoneAccountHandle phoneAccountHandle, int... states) {
+        Stream<Call> callsStream = getCallsWithState(callFilter, excludeCall, states);
+
+        // If a phone account handle was specified, only consider calls not associated with that
+        // phone account.
+        if (phoneAccountHandle != null) {
+            callsStream = callsStream.filter(
+                    call -> !phoneAccountHandle.equals(call.getTargetPhoneAccount()));
+        }
+
+        return (int) callsStream.count();
+    }
+
+    private Stream<Call> getCallsWithState(final int callFilter, Call excludeCall, int... states) {
         Set<Integer> desiredStates = IntStream.of(states).boxed().collect(Collectors.toSet());
 
         Stream<Call> callsStream = mCalls.stream()
@@ -5164,15 +5164,8 @@
             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()));
-        }
-
-        return (int) callsStream.count();
+        return callsStream;
     }
-
     /**
      * 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
@@ -5189,7 +5182,7 @@
      *                    {@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.
+     *                           are included in the count.
      * @param states The list of {@link CallState}s to include in the count.
      * @return Count of calls matching criteria.
      */
@@ -5571,12 +5564,41 @@
             return true;
         }
 
-        CompletableFuture<Boolean> disconnectFuture =
-                maybeDisconnectExistingCallForNewOutgoingCall(call, liveCall);
-        // If future is instantiated, it will always be completed when call sequencing
-        // isn't enabled.
-        if (!mFeatureFlags.enableCallSequencing() && disconnectFuture != null) {
-            return disconnectFuture.getNow(false);
+        // 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())) {
+            if (mFeatureFlags.telecomMetricsSupport()) {
+                mMetricsController.getErrorStats().log(ErrorStats.SUB_CALL_MANAGER,
+                        ErrorStats.ERROR_STUCK_CONNECTING);
+            }
+            mAnomalyReporter.reportAnomaly(LIVE_CALL_STUCK_CONNECTING_ERROR_UUID,
+                    LIVE_CALL_STUCK_CONNECTING_ERROR_MSG);
+            liveCall.disconnect("Force disconnect CONNECTING call.");
+            return true;
+        }
+
+        if (hasMaximumOutgoingCalls(call)) {
+            Call outgoingCall = getFirstCallWithState(OUTGOING_CALL_STATES);
+            if (outgoingCall.getState() == CallState.SELECT_PHONE_ACCOUNT) {
+                // If there is an orphaned call in the {@link CallState#SELECT_PHONE_ACCOUNT}
+                // state, just disconnect it since the user has explicitly started a new call.
+                call.getAnalytics().setCallIsAdditional(true);
+                outgoingCall.getAnalytics().setCallIsInterrupted(true);
+                outgoingCall.disconnect("Disconnecting call in SELECT_PHONE_ACCOUNT in favor"
+                        + " of new outgoing call.");
+                return true;
+            }
+            call.setStartFailCause(CallFailureCause.MAX_OUTGOING_CALLS);
+            return false;
         }
 
         // TODO: Remove once b/23035408 has been corrected.
@@ -5642,64 +5664,6 @@
     }
 
     /**
-     * Potentially disconnects the live call if it has been stuck in a connecting state for more
-     * than the designated timeout or the outgoing call if it's stuck in the
-     * {@link CallState#SELECT_PHONE_ACCOUNT} stage.
-     *
-     * @param call The new outgoing call that is being placed.
-     * @param liveCall The first live call that has been detected.
-     * @return The {@link CompletableFuture<Boolean>} representing if room for the outgoing call
-     * could be made, null if further processing is required.
-     */
-    public CompletableFuture<Boolean> maybeDisconnectExistingCallForNewOutgoingCall(Call call,
-            Call liveCall) {
-        // 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())) {
-            if (mFeatureFlags.telecomMetricsSupport()) {
-                mMetricsController.getErrorStats().log(ErrorStats.SUB_CALL_MANAGER,
-                        ErrorStats.ERROR_STUCK_CONNECTING);
-            }
-            mAnomalyReporter.reportAnomaly(LIVE_CALL_STUCK_CONNECTING_ERROR_UUID,
-                    LIVE_CALL_STUCK_CONNECTING_ERROR_MSG);
-            CompletableFuture<Boolean> disconnectFuture =
-                    liveCall.disconnect("Force disconnect CONNECTING call.");
-            return mFeatureFlags.enableCallSequencing()
-                    ? disconnectFuture
-                    : CompletableFuture.completedFuture(true);
-        }
-
-        if (hasMaximumOutgoingCalls(call)) {
-            Call outgoingCall = getFirstCallWithState(OUTGOING_CALL_STATES);
-            if (outgoingCall.getState() == CallState.SELECT_PHONE_ACCOUNT) {
-                // If there is an orphaned call in the {@link CallState#SELECT_PHONE_ACCOUNT}
-                // state, just disconnect it since the user has explicitly started a new call.
-                call.getAnalytics().setCallIsAdditional(true);
-                outgoingCall.getAnalytics().setCallIsInterrupted(true);
-                CompletableFuture<Boolean> disconnectFuture = outgoingCall.disconnect(
-                        "Disconnecting call in SELECT_PHONE_ACCOUNT in favor of new "
-                                + "outgoing call.");
-                return mFeatureFlags.enableCallSequencing()
-                        ? disconnectFuture
-                        : CompletableFuture.completedFuture(true);
-            }
-            call.setStartFailCause(CallFailureCause.MAX_OUTGOING_CALLS);
-            return CompletableFuture.completedFuture(false);
-        }
-
-        return null;
-    }
-
-    /**
      * Given a call, find the first non-null phone account handle of its children.
      *
      * @param parentCall The parent call.
@@ -6793,13 +6757,11 @@
                 Log.d(this, "perform unhold call for %s", mCall);
                 CompletableFuture<Boolean> unholdFuture =
                         mCall.unhold("held " + mPreviouslyHeldCallId);
-                if (mFeatureFlags.enableCallSequencing() && unholdFuture != null) {
-                    mCallSequencingAdapter.logFutureResultTransaction(unholdFuture,
-                            "performAction", "AUC.pA", "performAction: unhold call transaction "
-                                    + "succeeded. Call state is active.",
-                            "performAction: unhold call transaction failed. Call state did not "
-                                    + "move to active in designated time.");
-                }
+                mCallSequencingAdapter.maybeLogFutureResultTransaction(unholdFuture,
+                        "performAction", "AUC.pA", "performAction: unhold call transaction "
+                                + "succeeded. Call state is active.",
+                        "performAction: unhold call transaction failed. Call state did not "
+                                + "move to active in designated time.");
             }
         }
     }
@@ -6842,13 +6804,11 @@
                 if (isSpeakerphoneAutoEnabledForVideoCalls(mVideoState)) {
                     mCall.setStartWithSpeakerphoneOn(true);
                 }
-                if (mFeatureFlags.enableCallSequencing() && answerCallFuture != null) {
-                    mCallSequencingAdapter.logFutureResultTransaction(answerCallFuture,
-                            "performAction", "AAC.pA", "performAction: answer call transaction "
-                                    + "succeeded. Call state is active.",
-                            "performAction: answer call transaction failed. Call state did not "
-                                    + "move to active in designated time.");
-                }
+                mCallSequencingAdapter.maybeLogFutureResultTransaction(answerCallFuture,
+                        "performAction", "AAC.pA", "performAction: answer call transaction "
+                                + "succeeded. Call state is active.",
+                        "performAction: answer call transaction failed. Call state did not "
+                                + "move to active in designated time.");
             }
         }
     }
@@ -6934,8 +6894,12 @@
                 if (mTargetCallFocus.getState() != mPreviousCallState) {
                     mTargetCallFocus.setState(mPreviousCallState, "resetting call state");
                 }
-                mCallback.onError(new CallException("failed to switch focus to requested call",
+                String msg = "failed to switch focus to requested call";
+                mCallback.onError(new CallException(msg,
                         CallException.CODE_CALL_CANNOT_BE_SET_TO_ACTIVE));
+                if (mFeatureFlags.enableCallExceptionAnomReports()) {
+                    mAnomalyReporter.reportAnomaly(FAILED_TO_SWITCH_FOCUS_ERROR_UUID, msg);
+                }
                 return;
             }
             // at this point, we know the FocusManager is able to update successfully
@@ -7162,4 +7126,28 @@
     public void addCallBeingSetup(Call call) {
         mSelfManagedCallsBeingSetup.add(call);
     }
+
+    @VisibleForTesting
+    public CallsManagerCallSequencingAdapter getCallSequencingAdapter() {
+        return mCallSequencingAdapter;
+    }
+
+    public void waitForAudioToUpdate(boolean expectActive) {
+        Log.i(this, "waitForAudioToUpdate");
+        if (mFeatureFlags.useRefactoredAudioRouteSwitching()) {
+            try {
+                CallAudioRouteController audioRouteController =
+                        (CallAudioRouteController) mCallAudioRouteAdapter;
+                if (expectActive) {
+                    audioRouteController.getAudioActiveCompleteLatch().await(
+                            WAIT_FOR_AUDIO_UPDATE_TIMEOUT, TimeUnit.MILLISECONDS);
+                } else {
+                    audioRouteController.getAudioOperationsCompleteLatch().await(
+                            WAIT_FOR_AUDIO_UPDATE_TIMEOUT, TimeUnit.MILLISECONDS);
+                }
+            } catch (InterruptedException e) {
+                Log.w(this, e.toString());
+            }
+        }
+    }
 }
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index a125407..ef8210d 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -80,7 +80,6 @@
 import com.android.internal.telecom.ICallEventCallback;
 import com.android.internal.telecom.ITelecomService;
 import com.android.internal.util.IndentingPrintWriter;
-import com.android.server.telecom.callsequencing.voip.OutgoingCallTransactionSequencing;
 import com.android.server.telecom.callsequencing.voip.VoipCallMonitor;
 import com.android.server.telecom.components.UserCallIntentProcessorFactory;
 import com.android.server.telecom.flags.FeatureFlags;
@@ -141,6 +140,13 @@
             UUID.fromString("4edf6c8d-1e43-4c94-b0fc-a40c8d80cfe8");
     public static final String PLACE_CALL_SECURITY_EXCEPTION_ERROR_MSG =
             "Security exception thrown while placing an outgoing call.";
+    public static final UUID CALL_IS_NULL_OR_ID_MISMATCH_UUID =
+            UUID.fromString("b11f3251-474c-4f90-96d6-a256aebc3c19");
+    public static final String CALL_IS_NULL_OR_ID_MISMATCH_MSG =
+            "call is null or id mismatch";
+    public static final UUID ADD_CALL_ON_ERROR_UUID =
+            UUID.fromString("f8e7d6c5-b4a3-9210-8765-432109abcdef");
+
     private static final String TAG = "TelecomServiceImpl";
     private static final String TIME_LINE_ARG = "timeline";
     private static final int DEFAULT_VIDEO_STATE = -1;
@@ -233,6 +239,11 @@
                                     onAddCallControl(callId, callEventCallback, null,
                                             new CallException(ADD_CALL_ERR_MSG,
                                                     CODE_ERROR_UNKNOWN));
+                                    if (mFeatureFlags.enableCallExceptionAnomReports()) {
+                                        mAnomalyReporter.reportAnomaly(
+                                                CALL_IS_NULL_OR_ID_MISMATCH_UUID,
+                                                CALL_IS_NULL_OR_ID_MISMATCH_MSG);
+                                    }
                                     return;
                                 }
 
@@ -262,6 +273,11 @@
                             public void onError(@NonNull CallException exception) {
                                 Log.d(TAG, "addCall: onError: e=[%s]", exception.toString());
                                 onAddCallControl(callId, callEventCallback, null, exception);
+                                if (mFeatureFlags.enableCallExceptionAnomReports()) {
+                                    mAnomalyReporter.reportAnomaly(
+                                            ADD_CALL_ON_ERROR_UUID,
+                                            exception.getMessage());
+                                }
                             }
                         });
                     }
@@ -2929,6 +2945,17 @@
             }
         }
 
+        @Override
+        public void setMetricsTestMode(boolean enabled) {
+            if (mFeatureFlags.telecomMetricsSupport()) {
+                mMetricsController.setTestMode(enabled);
+            }
+        }
+
+        @Override
+        public void waitForAudioToUpdate(boolean expectActive) {
+            mCallsManager.waitForAudioToUpdate(expectActive);
+        }
         /**
          * Determines whether there are any ongoing {@link PhoneAccount#CAPABILITY_SELF_MANAGED}
          * calls for a given {@code packageName} and {@code userHandle}.
@@ -3018,7 +3045,10 @@
         });
 
         mTransactionManager = TransactionManager.getInstance();
-        mTransactionalServiceRepository = new TransactionalServiceRepository(mFeatureFlags);
+        mTransactionManager.setFeatureFlag(mFeatureFlags);
+        mTransactionManager.setAnomalyReporter(mAnomalyReporter);
+        mTransactionalServiceRepository = new TransactionalServiceRepository(mFeatureFlags,
+                mAnomalyReporter);
         mBlockedNumbersManager = mFeatureFlags.telecomMainlineBlockedNumbersManager()
                 ? mContext.getSystemService(BlockedNumbersManager.class)
                 : null;
diff --git a/src/com/android/server/telecom/TelecomShellCommand.java b/src/com/android/server/telecom/TelecomShellCommand.java
index 11ceb26..2e955a9 100644
--- a/src/com/android/server/telecom/TelecomShellCommand.java
+++ b/src/com/android/server/telecom/TelecomShellCommand.java
@@ -67,6 +67,10 @@
     private static final String COMMAND_RESET_CAR_MODE = "reset-car-mode";
     private static final String COMMAND_IS_NON_IN_CALL_SERVICE_BOUND =
             "is-non-ui-in-call-service-bound";
+    private static final String COMMAND_WAIT_FOR_AUDIO_OPS_COMPLETION =
+            "wait-for-audio-ops-complete";
+    private static final String COMMAND_WAIT_FOR_AUDIO_ACTIVE_COMPLETION =
+            "wait-for-audio-active";
 
     /**
      * Change the system dialer package name if a package name was specified,
@@ -83,6 +87,8 @@
     private static final String COMMAND_GET_MAX_PHONES = "get-max-phones";
     private static final String COMMAND_SET_TEST_EMERGENCY_PHONE_ACCOUNT_PACKAGE_FILTER =
             "set-test-emergency-phone-account-package-filter";
+    private static final String COMMAND_SET_METRICS_TEST_ENABLED = "set-metrics-test-enabled";
+    private static final String COMMAND_SET_METRICS_TEST_DISABLED = "set-metrics-test-disabled";
     /**
      * Command used to emit a distinct "mark" in the logs.
      */
@@ -184,6 +190,18 @@
                 case COMMAND_LOG_MARK:
                     runLogMark();
                     break;
+                case COMMAND_SET_METRICS_TEST_ENABLED:
+                    mTelecomService.setMetricsTestMode(true);
+                    break;
+                case COMMAND_SET_METRICS_TEST_DISABLED:
+                    mTelecomService.setMetricsTestMode(false);
+                    break;
+                case COMMAND_WAIT_FOR_AUDIO_OPS_COMPLETION:
+                    mTelecomService.waitForAudioToUpdate(false);
+                    break;
+                case COMMAND_WAIT_FOR_AUDIO_ACTIVE_COMPLETION:
+                    mTelecomService.waitForAudioToUpdate(true);
+                    break;
                 default:
                     return handleDefaultCommands(command);
             }
@@ -262,6 +280,8 @@
                 + "testers to indicate where in the logs various test steps take place.\n"
                 + "telecom is-non-ui-in-call-service-bound <PACKAGE>: queries a particular "
                 + "non-ui-InCallService in InCallController to determine if it is bound \n"
+                + "telecom set-metrics-test-enabled: Enable the metrics test mode.\n"
+                + "telecom set-metrics-test-disabled: Disable the metrics test mode.\n"
         );
     }
     private void runSetPhoneAccountEnabled(boolean enabled) throws RemoteException {
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index 7020885..b66fb10 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -28,6 +28,7 @@
 import android.net.Uri;
 import android.os.BugreportManager;
 import android.os.DropBoxManager;
+import android.os.Looper;
 import android.os.UserHandle;
 import android.telecom.Log;
 import android.telecom.PhoneAccountHandle;
@@ -230,7 +231,8 @@
             Executor asyncCallAudioTaskExecutor,
             BlockedNumbersAdapter blockedNumbersAdapter,
             FeatureFlags featureFlags,
-            com.android.internal.telephony.flags.FeatureFlags telephonyFlags) {
+            com.android.internal.telephony.flags.FeatureFlags telephonyFlags,
+            Looper looper) {
         mContext = context.getApplicationContext();
         mFeatureFlags = featureFlags;
         LogUtils.initLogging(mContext);
@@ -264,7 +266,7 @@
                     communicationDeviceTracker, featureFlags);
             BluetoothRouteManager bluetoothRouteManager = new BluetoothRouteManager(mContext, mLock,
                     bluetoothDeviceManager, new Timeouts.Adapter(),
-                    communicationDeviceTracker, featureFlags);
+                    communicationDeviceTracker, featureFlags, looper);
             BluetoothStateReceiver bluetoothStateReceiver = new BluetoothStateReceiver(
                     bluetoothDeviceManager, bluetoothRouteManager,
                     communicationDeviceTracker, featureFlags);
diff --git a/src/com/android/server/telecom/TransactionalServiceRepository.java b/src/com/android/server/telecom/TransactionalServiceRepository.java
index 5ae459e..954307a 100644
--- a/src/com/android/server/telecom/TransactionalServiceRepository.java
+++ b/src/com/android/server/telecom/TransactionalServiceRepository.java
@@ -35,9 +35,13 @@
     private static final Map<PhoneAccountHandle, TransactionalServiceWrapper> mServiceLookupTable =
             new HashMap<>();
     private final FeatureFlags mFlags;
+    private final AnomalyReporterAdapter mAnomalyReporter;
 
-    public TransactionalServiceRepository(FeatureFlags flags) {
+    public TransactionalServiceRepository(
+            FeatureFlags flags,
+            AnomalyReporterAdapter anomalyReporter) {
         mFlags = flags;
+        mAnomalyReporter = anomalyReporter;
     }
 
     public TransactionalServiceWrapper addNewCallForTransactionalServiceWrapper
@@ -50,7 +54,8 @@
             Log.d(TAG, "creating a new TSW; handle=[%s]", phoneAccountHandle);
             service = new TransactionalServiceWrapper(callEventCallback,
                     callsManager, phoneAccountHandle, call, this,
-                    TransactionManager.getInstance(), mFlags.enableCallSequencing());
+                    TransactionManager.getInstance(), mFlags.enableCallSequencing(),
+                    mFlags, mAnomalyReporter);
         } else {
             Log.d(TAG, "add a new call to an existing TSW; handle=[%s]", phoneAccountHandle);
             service = getTransactionalServiceWrapper(phoneAccountHandle);
diff --git a/src/com/android/server/telecom/TransactionalServiceWrapper.java b/src/com/android/server/telecom/TransactionalServiceWrapper.java
index d63a0bd..cc0d547 100644
--- a/src/com/android/server/telecom/TransactionalServiceWrapper.java
+++ b/src/com/android/server/telecom/TransactionalServiceWrapper.java
@@ -47,9 +47,11 @@
 import com.android.server.telecom.callsequencing.TransactionManager;
 import com.android.server.telecom.callsequencing.CallTransaction;
 import com.android.server.telecom.callsequencing.CallTransactionResult;
+import com.android.server.telecom.flags.FeatureFlags;
 
 import java.util.Locale;
 import java.util.Set;
+import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
 
@@ -92,7 +94,12 @@
     private TransactionManager mTransactionManager;
     private CallStreamingController mStreamingController;
     private final TransactionalCallSequencingAdapter mCallSequencingAdapter;
-
+    private final FeatureFlags mFeatureFlags;
+    private final AnomalyReporterAdapter mAnomalyReporter;
+    public static final UUID CALL_IS_NO_LONGER_BEING_TRACKED_ERROR_UUID =
+            UUID.fromString("8187cd59-97a7-4e9f-a772-638dda4b69bb");
+    public static final String CALL_IS_NO_LONGER_BEING_TRACKED_ERROR_MSG =
+            "A call update was attempted for a call no longer being tracked";
 
     // Each TransactionalServiceWrapper should have their own Binder.DeathRecipient to clean up
     // any calls in the event the application crashes or is force stopped.
@@ -108,7 +115,8 @@
     public TransactionalServiceWrapper(ICallEventCallback callEventCallback,
             CallsManager callsManager, PhoneAccountHandle phoneAccountHandle, Call call,
             TransactionalServiceRepository repo, TransactionManager transactionManager,
-            boolean isCallSequencingEnabled) {
+            boolean isCallSequencingEnabled, FeatureFlags featureFlags,
+            AnomalyReporterAdapter anomalyReporterAdapter) {
         // passed args
         mICallEventCallback = callEventCallback;
         mCallsManager = callsManager;
@@ -123,6 +131,8 @@
         mCallSequencingAdapter = new TransactionalCallSequencingAdapter(mTransactionManager,
                 mCallsManager, isCallSequencingEnabled);
         setDeathRecipient(callEventCallback);
+        mFeatureFlags = featureFlags;
+        mAnomalyReporter = anomalyReporterAdapter;
     }
 
     public TransactionManager getTransactionManager() {
@@ -307,6 +317,11 @@
                                 + " via TelecomManager#addCall", action, callId),
                                 CODE_CALL_IS_NOT_BEING_TRACKED));
                 callback.send(CODE_CALL_IS_NOT_BEING_TRACKED, exceptionBundle);
+                if (mFeatureFlags.enableCallExceptionAnomReports()) {
+                    mAnomalyReporter.reportAnomaly(
+                            CALL_IS_NO_LONGER_BEING_TRACKED_ERROR_UUID,
+                            CALL_IS_NO_LONGER_BEING_TRACKED_ERROR_MSG);
+                }
             }
         }
 
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
index 8d7c2bf..64c7f33 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
@@ -248,14 +248,17 @@
                 .getBluetoothRoutes();
         List<Pair<AudioRoute, BluetoothDevice>> btRoutesToRemove =
                 new ArrayList<>();
-        for (AudioRoute route: btRoutes.keySet()) {
-            if (route.getType() != PROFILE_TO_AUDIO_ROUTE_MAP.get(profile)) {
-                continue;
+        // Prevent concurrent modification exception by just iterating
+        //through keys instead of simultaneously removing them. Ensure that
+        // we synchronize on the map while we traverse via an Iterator.
+        synchronized (btRoutes) {
+            for (AudioRoute route: btRoutes.keySet()) {
+                if (route.getType() != PROFILE_TO_AUDIO_ROUTE_MAP.get(profile)) {
+                    continue;
+                }
+                BluetoothDevice device = btRoutes.get(route);
+                btRoutesToRemove.add(new Pair<>(route, device));
             }
-            BluetoothDevice device = btRoutes.get(route);
-            // Prevent concurrent modification exception by just iterating through keys instead of
-            // simultaneously removing them.
-            btRoutesToRemove.add(new Pair<>(route, device));
         }
 
         for (Pair<AudioRoute, BluetoothDevice> routeToRemove:
@@ -602,7 +605,8 @@
             Log.w(this, "disconnectSco: Trying to disconnect audio but no headset service exists.");
         } else {
             result = mBluetoothHeadset.disconnectAudio();
-            Log.i(this, "disconnectSco: BluetoothHeadset#disconnectAudio()=%b", result);
+            Log.i(this, "disconnectSco: BluetoothHeadset#disconnectAudio()=%s",
+                    btCodeToString(result));
         }
         return result;
     }
@@ -848,6 +852,7 @@
         if (callProfile == BluetoothProfile.LE_AUDIO) {
             if (mBluetoothAdapter.setActiveDevice(
                     device, BluetoothAdapter.ACTIVE_DEVICE_ALL)) {
+                Log.i(this, "connectAudio: BluetoothAdapter#setActiveDevice(%s)=true", address);
                 /* ACTION_ACTIVE_DEVICE_CHANGED intent will trigger setting communication device.
                  * Only after receiving ACTION_ACTIVE_DEVICE_CHANGED it is known that device that
                  * will be audio switched to is available to be choose as communication device */
@@ -859,9 +864,11 @@
                 }
                 return true;
             }
+            Log.i(this, "connectAudio: BluetoothAdapter#setActiveDevice(%s)=false", address);
             return false;
         } else if (callProfile == BluetoothProfile.HEARING_AID) {
             if (mBluetoothAdapter.setActiveDevice(device, BluetoothAdapter.ACTIVE_DEVICE_ALL)) {
+                Log.i(this, "connectAudio: BluetoothAdapter#setActiveDevice(%s)=true", address);
                 /* ACTION_ACTIVE_DEVICE_CHANGED intent will trigger setting communication device.
                  * Only after receiving ACTION_ACTIVE_DEVICE_CHANGED it is known that device that
                  * will be audio switched to is available to be choose as communication device */
@@ -873,19 +880,20 @@
                 }
                 return true;
             }
+            Log.i(this, "connectAudio: BluetoothAdapter#setActiveDevice(%s)=false", address);
             return false;
         } else if (callProfile == BluetoothProfile.HEADSET) {
             boolean success = mBluetoothAdapter.setActiveDevice(device,
                 BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL);
+            Log.i(this, "connectAudio: BluetoothAdapter#setActiveDevice(%s)=%b", address, success);
             if (!success) {
                 Log.w(this, "connectAudio: Couldn't set active device to %s", address);
                 return false;
             }
-            Log.i(this, "connectAudio: BluetoothAdapter#setActiveDevice(%s)", address);
             if (getBluetoothHeadset() != null) {
                 int scoConnectionRequest = mBluetoothHeadset.connectAudio();
-                Log.i(this, "connectAudio: BluetoothHeadset#connectAudio()=%d",
-                        scoConnectionRequest);
+                Log.i(this, "connectAudio: BluetoothHeadset#connectAudio()=%s",
+                        btCodeToString(scoConnectionRequest));
                 return scoConnectionRequest == BluetoothStatusCodes.SUCCESS ||
                         scoConnectionRequest
                                 == BluetoothStatusCodes.ERROR_AUDIO_DEVICE_ALREADY_CONNECTED;
@@ -926,18 +934,22 @@
 
         if (callProfile == BluetoothProfile.LE_AUDIO
                 || callProfile == BluetoothProfile.HEARING_AID || isScoManagedByAudio) {
-            return mBluetoothAdapter.setActiveDevice(device, BluetoothAdapter.ACTIVE_DEVICE_ALL);
+            boolean success = mBluetoothAdapter.setActiveDevice(device,
+                    BluetoothAdapter.ACTIVE_DEVICE_ALL);
+            Log.i(this, "connectAudio: BluetoothAdapter#setActiveDevice(%s)=%b", address, success);
+            return success;
         } else if (callProfile == BluetoothProfile.HEADSET) {
             boolean success = mBluetoothAdapter.setActiveDevice(device,
                     BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL);
+            Log.i(this, "connectAudio: BluetoothAdapter#setActiveDevice(%s)=%b", address, success);
             if (!success) {
                 Log.w(this, "connectAudio: Couldn't set active device to %s", address);
                 return false;
             }
             if (getBluetoothHeadset() != null) {
                 int scoConnectionRequest = mBluetoothHeadset.connectAudio();
-                Log.i(this, "connectaudio: BluetoothHeadset#connectAudio()=%d",
-                        scoConnectionRequest);
+                Log.i(this, "connectAudio: BluetoothHeadset#connectAudio()=%s",
+                        btCodeToString(scoConnectionRequest));
                 return scoConnectionRequest == BluetoothStatusCodes.SUCCESS ||
                         scoConnectionRequest
                                 == BluetoothStatusCodes.ERROR_AUDIO_DEVICE_ALREADY_CONNECTED;
@@ -1007,4 +1019,33 @@
     public void dump(IndentingPrintWriter pw) {
         mLocalLog.dump(pw);
     }
+
+    private String btCodeToString(int code) {
+        switch (code) {
+            case BluetoothStatusCodes.SUCCESS:
+                return "SUCCESS";
+            case BluetoothStatusCodes.ERROR_UNKNOWN:
+                return "ERROR_UNKNOWN";
+            case BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND:
+                return "ERROR_PROFILE_SERVICE_NOT_BOUND";
+            case BluetoothStatusCodes.ERROR_TIMEOUT:
+                return "ERROR_TIMEOUT";
+            case BluetoothStatusCodes.ERROR_AUDIO_DEVICE_ALREADY_CONNECTED:
+                return "ERROR_AUDIO_DEVICE_ALREADY_CONNECTED";
+            case BluetoothStatusCodes.ERROR_NO_ACTIVE_DEVICES:
+                return "ERROR_NO_ACTIVE_DEVICES";
+            case BluetoothStatusCodes.ERROR_NOT_ACTIVE_DEVICE:
+                return "ERROR_NOT_ACTIVE_DEVICE";
+            case BluetoothStatusCodes.ERROR_AUDIO_ROUTE_BLOCKED:
+                return "ERROR_AUDIO_ROUTE_BLOCKED";
+            case BluetoothStatusCodes.ERROR_CALL_ACTIVE:
+                return "ERROR_CALL_ACTIVE";
+            case BluetoothStatusCodes.ERROR_PROFILE_NOT_CONNECTED:
+                return "ERROR_PROFILE_NOT_CONNECTED";
+            case BluetoothStatusCodes.ERROR_AUDIO_DEVICE_ALREADY_DISCONNECTED:
+                return "BluetoothStatusCodes.ERROR_AUDIO_DEVICE_ALREADY_DISCONNECTED";
+            default:
+                return Integer.toString(code);
+        }
+    }
 }
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
index 5a44041..6e9af8a 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
@@ -26,6 +26,7 @@
 import android.content.Context;
 import android.media.AudioDeviceInfo;
 import android.os.Message;
+import android.os.Looper;
 import android.telecom.Log;
 import android.telecom.Logging.Session;
 import android.util.Pair;
@@ -607,8 +608,8 @@
     public BluetoothRouteManager(Context context, TelecomSystem.SyncRoot lock,
             BluetoothDeviceManager deviceManager, Timeouts.Adapter timeoutsAdapter,
             CallAudioCommunicationDeviceTracker communicationDeviceTracker,
-            FeatureFlags featureFlags) {
-        super(BluetoothRouteManager.class.getSimpleName());
+            FeatureFlags featureFlags, Looper looper) {
+        super(BluetoothRouteManager.class.getSimpleName(), looper);
         mContext = context;
         mLock = lock;
         mDeviceManager = deviceManager;
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
index beddcbe..1330be4 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
@@ -272,6 +272,10 @@
                 if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID
                         || deviceType == BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO
                         || mIsScoManagedByAudio) {
+                    if (!mIsInCall) {
+                        Log.i(LOG_TAG, "Ignoring audio on since we're not in a call");
+                        return;
+                    }
                     if (!mBluetoothDeviceManager.setCommunicationDeviceForAddress(
                             device.getAddress())) {
                         Log.i(this, "handleActiveDeviceChanged: Failed to set "
diff --git a/src/com/android/server/telecom/callsequencing/CallSequencingController.java b/src/com/android/server/telecom/callsequencing/CallSequencingController.java
index a6744e5..713d155 100644
--- a/src/com/android/server/telecom/callsequencing/CallSequencingController.java
+++ b/src/com/android/server/telecom/callsequencing/CallSequencingController.java
@@ -18,12 +18,15 @@
 
 import static android.Manifest.permission.CALL_PRIVILEGED;
 
+import static com.android.server.telecom.CallsManager.CALL_FILTER_ALL;
 import static com.android.server.telecom.CallsManager.LIVE_CALL_STUCK_CONNECTING_EMERGENCY_ERROR_MSG;
 import static com.android.server.telecom.CallsManager.LIVE_CALL_STUCK_CONNECTING_EMERGENCY_ERROR_UUID;
+import static com.android.server.telecom.CallsManager.LIVE_CALL_STUCK_CONNECTING_ERROR_MSG;
+import static com.android.server.telecom.CallsManager.LIVE_CALL_STUCK_CONNECTING_ERROR_UUID;
+import static com.android.server.telecom.CallsManager.ONGOING_CALL_STATES;
 import static com.android.server.telecom.CallsManager.OUTGOING_CALL_STATES;
 import static com.android.server.telecom.UserUtil.showErrorDialogForRestrictedOutgoingCall;
 
-import android.annotation.NonNull;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -43,18 +46,24 @@
 import android.telephony.CarrierConfigManager;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.telecom.AnomalyReporterAdapter;
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallState;
 import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.ClockProxy;
 import com.android.server.telecom.LogUtils;
 import com.android.server.telecom.LoggedHandlerExecutor;
 import com.android.server.telecom.R;
+import com.android.server.telecom.Timeouts;
 import com.android.server.telecom.callsequencing.voip.OutgoingCallTransaction;
 import com.android.server.telecom.callsequencing.voip.OutgoingCallTransactionSequencing;
 import com.android.server.telecom.flags.FeatureFlags;
+import com.android.server.telecom.metrics.ErrorStats;
+import com.android.server.telecom.metrics.TelecomMetricsController;
 import com.android.server.telecom.stats.CallFailureCause;
 
 import java.util.Objects;
+import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 
 /**
@@ -66,19 +75,31 @@
  */
 public class CallSequencingController {
     private final CallsManager mCallsManager;
+    private final ClockProxy mClockProxy;
+    private final AnomalyReporterAdapter mAnomalyReporter;
+    private final Timeouts.Adapter mTimeoutsAdapter;
+    private final TelecomMetricsController mMetricsController;
     private final Handler mHandler;
     private final Context mContext;
     private final FeatureFlags mFeatureFlags;
-    private boolean mProcessingCallSequencing;
     private static String TAG = CallSequencingController.class.getSimpleName();
+    public static final UUID SEQUENCING_CANNOT_HOLD_ACTIVE_CALL_UUID =
+            UUID.fromString("ea094d77-6ea9-4e40-891e-14bff5d485d7");
+    public static final String SEQUENCING_CANNOT_HOLD_ACTIVE_CALL_MSG =
+            "Cannot hold active call";
 
     public CallSequencingController(CallsManager callsManager, Context context,
+            ClockProxy clockProxy, AnomalyReporterAdapter anomalyReporter,
+            Timeouts.Adapter timeoutsAdapter, TelecomMetricsController metricsController,
             FeatureFlags featureFlags) {
         mCallsManager = callsManager;
+        mClockProxy = clockProxy;
+        mAnomalyReporter = anomalyReporter;
+        mMetricsController = metricsController;
+        mTimeoutsAdapter = timeoutsAdapter;
         HandlerThread handlerThread = new HandlerThread(this.toString());
         handlerThread.start();
         mHandler = new Handler(handlerThread.getLooper());
-        mProcessingCallSequencing = false;
         mFeatureFlags = featureFlags;
         mContext = context;
     }
@@ -121,16 +142,18 @@
             if (callFuture == null) {
                 Log.d(this, "createTransactionalOutgoingCall: Outgoing call not permitted at the "
                         + "current time.");
-                return CompletableFuture.completedFuture(null);
+                return CompletableFuture.completedFuture(new OutgoingCallTransactionSequencing(
+                        mCallsManager, null, true /* callNotPermitted */, mFeatureFlags));
             }
             return callFuture.thenComposeAsync((call) -> CompletableFuture.completedFuture(
                     new OutgoingCallTransactionSequencing(mCallsManager, callFuture,
-                            mFeatureFlags)),
+                            false /* callNotPermitted */, mFeatureFlags)),
                     new LoggedHandlerExecutor(mHandler, "CSC.aC", mCallsManager.getLock()));
         } else {
             Log.d(this, "createTransactionalOutgoingCall: outgoing call not permitted at the "
                     + "current time.");
-            return CompletableFuture.completedFuture(null);
+            return CompletableFuture.completedFuture(new OutgoingCallTransactionSequencing(
+                    mCallsManager, null, true /* callNotPermitted */, mFeatureFlags));
         }
     }
 
@@ -143,14 +166,7 @@
     public void answerCall(Call incomingCall, int videoState) {
         Log.i(this, "answerCall: Beginning call sequencing transaction for answering "
                 + "incoming call.");
-        // Retrieve the CompletableFuture which processes the steps to make room to answer the
-        // incoming call.
-        CompletableFuture<Boolean> holdActiveForNewCallFutureHandler =
-                holdActiveCallForNewCallWithSequencing(incomingCall);
-        // If we're performing call sequencing across phone accounts, then ensure that we only
-        // proceed if the future above has completed successfully.
-        if (isProcessingCallSequencing()) {
-            holdActiveForNewCallFutureHandler.thenComposeAsync((result) -> {
+        holdActiveCallForNewCallWithSequencing(incomingCall).thenComposeAsync((result) -> {
                 if (result) {
                     mCallsManager.requestFocusActionAnswerCall(incomingCall, videoState);
                 } else {
@@ -159,11 +175,7 @@
                 }
                 return CompletableFuture.completedFuture(result);
             }, new LoggedHandlerExecutor(mHandler, "CSC.aC",
-                    mCallsManager.getLock()));
-        } else {
-            mCallsManager.requestFocusActionAnswerCall(incomingCall, videoState);
-        }
-        resetProcessingCallSequencing();
+                mCallsManager.getLock()));
     }
 
     /**
@@ -171,10 +183,7 @@
      * @param call The self-managed call that's waiting to go active.
      */
     public void handleSetSelfManagedCallActive(Call call) {
-        CompletableFuture<Boolean> holdActiveCallFuture =
-                holdActiveCallForNewCallWithSequencing(call);
-        if (isProcessingCallSequencing()) {
-            holdActiveCallFuture.thenComposeAsync((result) -> {
+        holdActiveCallForNewCallWithSequencing(call).thenComposeAsync((result) -> {
                 if (result) {
                     Log.i(this, "markCallAsActive: requesting focus for self managed call "
                             + "before setting active.");
@@ -186,12 +195,7 @@
                 }
                 return CompletableFuture.completedFuture(result);
             }, new LoggedHandlerExecutor(mHandler,
-                    "CM.mCAA", mCallsManager.getLock()));
-        } else {
-            mCallsManager.requestActionSetActiveCall(call,
-                    "active set explicitly for self-managed");
-        }
-        resetProcessingCallSequencing();
+                "CM.mCAA", mCallsManager.getLock()));
     }
 
     /**
@@ -207,8 +211,8 @@
      */
     public void transactionHoldPotentialActiveCallForNewCallSequencing(
             Call newCall, OutcomeReceiver<Boolean, CallException> callback) {
-        CompletableFuture<Boolean> holdActiveCallFuture =
-                holdActiveCallForNewCallWithSequencing(newCall).thenComposeAsync((result) -> {
+        holdActiveCallForNewCallWithSequencing(newCall)
+                .thenComposeAsync((result) -> {
                     if (result) {
                         // Either we were able to hold the active call or the active call was
                         // disconnected in favor of the new call.
@@ -219,10 +223,15 @@
                         callback.onError(
                                 new CallException("activeCall could not be held or disconnected",
                                 CallException.CODE_CANNOT_HOLD_CURRENT_ACTIVE_CALL));
+                        if (mFeatureFlags.enableCallExceptionAnomReports()) {
+                            mAnomalyReporter.reportAnomaly(
+                                    SEQUENCING_CANNOT_HOLD_ACTIVE_CALL_UUID,
+                                    SEQUENCING_CANNOT_HOLD_ACTIVE_CALL_MSG
+                            );
+                        }
                     }
                     return CompletableFuture.completedFuture(result);
                 }, new LoggedHandlerExecutor(mHandler, "CM.mCAA", mCallsManager.getLock()));
-        resetProcessingCallSequencing();
     }
 
     /**
@@ -230,19 +239,24 @@
      * call sequencing and the resulting future is an indication of whether that request
      * has succeeded.
      * @param call The call that's waiting to go active.
-     * @return The {@code CompletableFuture} indicating the result of whether the active call was
-     *         able to be held (if applicable).
+     * @return The {@link CompletableFuture} indicating the result of whether the
+     *         active call was able to be held (if applicable).
      */
-    CompletableFuture<Boolean> holdActiveCallForNewCallWithSequencing(Call call) {
+    @VisibleForTesting
+    public CompletableFuture<Boolean> holdActiveCallForNewCallWithSequencing(
+            Call call) {
         Call activeCall = (Call) mCallsManager.getConnectionServiceFocusManager()
                 .getCurrentFocusCall();
         Log.i(this, "holdActiveCallForNewCallWithSequencing, newCall: %s, "
                         + "activeCall: %s", call.getId(),
                 (activeCall == null ? "<none>" : activeCall.getId()));
         if (activeCall != null && activeCall != call) {
-            processCallSequencing(call, activeCall);
+            boolean isSequencingRequiredActiveAndCall = !arePhoneAccountsSame(call, activeCall);
             if (mCallsManager.canHold(activeCall)) {
-                return activeCall.hold("swap to " + call.getId());
+                CompletableFuture<Boolean> holdFuture = activeCall.hold("swap to " + call.getId());
+                return isSequencingRequiredActiveAndCall
+                        ? holdFuture
+                        : CompletableFuture.completedFuture(true);
             } else if (mCallsManager.supportsHold(activeCall)) {
                 // Handle the case where active call supports hold but can't currently be held.
                 // In this case, we'll look for the currently held call to disconnect prior to
@@ -264,80 +278,89 @@
                 // Otherwise, we can send the requests up til the focus call state in question.
                 Call heldCall = mCallsManager.getFirstCallWithState(CallState.ON_HOLD);
                 CompletableFuture<Boolean> disconnectFutureHandler = null;
-                // Assume default case (no sequencing required).
-                boolean areIncomingHeldFromSamePhoneAccount;
 
+                boolean isSequencingRequiredHeldAndActive = false;
                 if (heldCall != null) {
-                    processCallSequencing(heldCall, activeCall);
-                    processCallSequencing(call, heldCall);
-                    areIncomingHeldFromSamePhoneAccount = arePhoneAccountsSame(call, heldCall);
-
                     // If the calls are from the same source or the incoming call isn't a VOIP call
                     // and the held call is a carrier call, then disconnect the held call. The
                     // idea is that if we have a held carrier call and the incoming call is a
                     // VOIP call, we don't want to force the carrier call to auto-disconnect).
-                    if (areIncomingHeldFromSamePhoneAccount || !(call.isSelfManaged()
-                            && !heldCall.isSelfManaged())) {
+                    if (!heldCall.isSelfManaged() && call.isSelfManaged()) {
+                        // Otherwise, fail the transaction.
+                        return CompletableFuture.completedFuture(false);
+                    } else {
+                        isSequencingRequiredHeldAndActive = !arePhoneAccountsSame(
+                                heldCall, activeCall);
                         disconnectFutureHandler = heldCall.disconnect();
                         Log.i(this, "holdActiveCallForNewCallWithSequencing: "
                                         + "Disconnect held call %s before holding active call %s.",
                                 heldCall.getId(), activeCall.getId());
-                    } else {
-                        // Otherwise, fail the transaction.
-                        return CompletableFuture.completedFuture(false);
                     }
                 }
                 Log.i(this, "holdActiveCallForNewCallWithSequencing: Holding active "
                         + "%s before making %s active.", activeCall.getId(), call.getId());
 
                 CompletableFuture<Boolean> holdFutureHandler;
-                if (isProcessingCallSequencing() && disconnectFutureHandler != null) {
+                if (isSequencingRequiredHeldAndActive && disconnectFutureHandler != null) {
                     holdFutureHandler = disconnectFutureHandler
                             .thenComposeAsync((result) -> {
                                 if (result) {
-                                    return activeCall.hold();
+                                    return activeCall.hold().thenCompose((holdSuccess) -> {
+                                        if (holdSuccess) {
+                                            // Increase hold count only if hold succeeds.
+                                            call.increaseHeldByThisCallCount();
+                                        }
+                                        return CompletableFuture.completedFuture(holdSuccess);
+                                    });
                                 }
                                 return CompletableFuture.completedFuture(false);
                             }, new LoggedHandlerExecutor(mHandler,
                                     "CSC.hACFNCWS", mCallsManager.getLock()));
                 } else {
                     holdFutureHandler = activeCall.hold();
+                    call.increaseHeldByThisCallCount();
                 }
-                call.increaseHeldByThisCallCount();
-                return holdFutureHandler;
+                // Next transaction will be performed on the call passed in and the last transaction
+                // was performed on the active call so ensure that the caller has this information
+                // to determine if sequencing is required.
+                return isSequencingRequiredActiveAndCall
+                        ? holdFutureHandler
+                        : CompletableFuture.completedFuture(true);
             } else {
                 // This call does not support hold. If it is from a different connection
                 // service or connection manager, then disconnect it, otherwise allow the connection
                 // service or connection manager to figure out the right states.
-                if (isProcessingCallSequencing()) {
-                    Log.i(this, "holdActiveCallForNewCallWithSequencing: disconnecting %s "
-                            + "so that %s can be made active.", activeCall.getId(), call.getId());
-                    if (!activeCall.isEmergencyCall()) {
-                        // We don't want to allow VOIP apps to disconnect carrier calls. We are
-                        // purposely completing the future with false so that the call isn't
-                        // answered.
-                        if (call.isSelfManaged() && !activeCall.isSelfManaged()) {
-                            Log.w(this, "holdActiveCallForNewCallWithSequencing: ignore "
-                                    + "disconnecting carrier call for making VOIP call active");
-                            return CompletableFuture.completedFuture(false);
-                        } else {
-                            return activeCall.disconnect();
-                        }
+                Log.i(this, "holdActiveCallForNewCallWithSequencing: evaluating disconnecting %s "
+                        + "so that %s can be made active.", activeCall.getId(), call.getId());
+                if (!activeCall.isEmergencyCall()) {
+                    // We don't want to allow VOIP apps to disconnect carrier calls. We are
+                    // purposely completing the future with false so that the call isn't
+                    // answered.
+                    if (isSequencingRequiredActiveAndCall && call.isSelfManaged()
+                            && !activeCall.isSelfManaged()) {
+                        Log.w(this, "holdActiveCallForNewCallWithSequencing: ignore "
+                                + "disconnecting carrier call for making VOIP call active");
+                        return CompletableFuture.completedFuture(false);
                     } else {
-                        // It's not possible to hold the active call, and it's an emergency call so
-                        // we will silently reject the incoming call instead of answering it.
-                        Log.w(this, "holdActiveCallForNewCallWithSequencing: rejecting incoming "
-                                + "call %s as the active call is an emergency call and "
-                                + "it cannot be held.", call.getId());
-                        return call.reject(false /* rejectWithMessage */, "" /* message */,
-                                "active emergency call can't be held");
+                        if (isSequencingRequiredActiveAndCall) {
+                            return activeCall.disconnect("Active call disconnected in favor of"
+                                    + " new call.");
+                        } else {
+                            Log.i(this, "holdActiveCallForNewCallWithSequencing: "
+                                    + "allowing ConnectionService to determine how to handle "
+                                    + "this case");
+                            CompletableFuture.completedFuture(true);
+                        }
                     }
                 } else {
-                    // Same source case: if the active call cannot be held, then the user has
-                    // willingly chosen to accept the incoming call knowing that the active call
-                    // will be disconnected.
-                    return activeCall.disconnect("Active call disconnected in favor of accepting "
-                            + "incoming call.");
+                    // It's not possible to hold the active call, and it's an emergency call so
+                    // we will silently reject the incoming call instead of answering it.
+                    Log.w(this, "holdActiveCallForNewCallWithSequencing: rejecting incoming "
+                            + "call %s as the active call is an emergency call and "
+                            + "it cannot be held.", call.getId());
+                    call.reject(false /* rejectWithMessage */, "" /* message */,
+                            "active emergency call can't be held");
+                    return CompletableFuture.completedFuture(false);
                 }
             }
         }
@@ -356,11 +379,11 @@
         Call activeCall = (Call) mCallsManager.getConnectionServiceFocusManager()
                 .getCurrentFocusCall();
         String activeCallId = null;
+        boolean isSequencingRequiredActiveAndCall = false;
         if (activeCall != null && !activeCall.isLocallyDisconnecting()) {
             activeCallId = activeCall.getId();
             // Determine whether the calls are placed on different phone accounts.
-            boolean areFromSamePhoneAccount = arePhoneAccountsSame(activeCall, call);
-            processCallSequencing(activeCall, call);
+            isSequencingRequiredActiveAndCall = !arePhoneAccountsSame(activeCall, call);
             boolean canSwapCalls = canSwap(activeCall, call);
 
             // If the active + held call are from different phone accounts, ensure that the call
@@ -370,14 +393,16 @@
                 Log.addEvent(activeCall, LogUtils.Events.SWAP, "To " + call.getId());
                 Log.addEvent(call, LogUtils.Events.SWAP, "From " + activeCallId);
             } else {
-                if (!areFromSamePhoneAccount) {
-                    // Don't unhold the call as requested if the active and held call are on
-                    // different phone accounts - consider the WhatsApp (held) and PSTN (active)
-                    // case. We also don't want to drop an emergency call.
+                if (isSequencingRequiredActiveAndCall) {
+                    // If hold isn't supported and the active and held call are on
+                    // different phone accounts where the held call is self-managed and active call
+                    // is managed, abort the transaction. Otherwise, disconnect the call. We also
+                    // don't want to drop an emergency call.
                     if (!activeCall.isEmergencyCall()) {
-                        Log.w(this, "unholdCall: %s and %s are using different phone accounts. "
-                                        + "Aborting swap to %s", activeCallId, call.getId(),
+                        Log.w(this, "unholdCall: Unable to hold the active call (%s),"
+                                        + " aborting swap to %s", activeCallId, call.getId(),
                                 call.getId());
+                        showErrorDialogForCannotHoldCall(call, false);
                     } else {
                         Log.w(this, "unholdCall: %s is an emergency call, aborting swap to %s",
                                 activeCallId, call.getId());
@@ -390,7 +415,7 @@
         }
 
         // Verify call state was changed to ACTIVE state
-        if (isProcessingCallSequencing() && unholdCallFutureHandler != null) {
+        if (isSequencingRequiredActiveAndCall && unholdCallFutureHandler != null) {
             String fixedActiveCallId = activeCallId;
             // Only attempt to unhold call if previous request to hold/disconnect call (on different
             // phone account) succeeded.
@@ -409,15 +434,12 @@
             // Otherwise, we should verify call unhold succeeded for focus call.
             mCallsManager.requestActionUnholdCall(call, activeCallId);
         }
-        resetProcessingCallSequencing();
     }
 
     public CompletableFuture<Boolean> makeRoomForOutgoingCall(boolean isEmergency, Call call) {
-        CompletableFuture<Boolean> makeRoomForOutgoingCallFuture = isEmergency
+        return isEmergency
                 ? makeRoomForOutgoingEmergencyCall(call)
                 : makeRoomForOutgoingCall(call);
-        resetProcessingCallSequencing();
-        return makeRoomForOutgoingCallFuture;
     }
 
     /**
@@ -435,7 +457,6 @@
         Call ringingCall = null;
         if (mCallsManager.hasRingingOrSimulatedRingingCall()) {
             ringingCall = mCallsManager.getRingingOrSimulatedRingingCall();
-            processCallSequencing(ringingCall, emergencyCall);
             ringingCall.getAnalytics().setCallIsAdditional(true);
             ringingCall.getAnalytics().setCallIsInterrupted(true);
             if (ringingCall.getState() == CallState.SIMULATED_RINGING) {
@@ -494,8 +515,9 @@
                         + " of new outgoing call.";
             }
             if (disconnectReason != null) {
-                processCallSequencing(outgoingCall, emergencyCall);
-                if (ringingCallFuture != null && isProcessingCallSequencing()) {
+                boolean isSequencingRequiredRingingAndOutgoing = !arePhoneAccountsSame(
+                        ringingCall, outgoingCall);
+                if (ringingCallFuture != null && isSequencingRequiredRingingAndOutgoing) {
                     String finalDisconnectReason = disconnectReason;
                     return ringingCallFuture.thenComposeAsync((result) -> {
                         if (result) {
@@ -521,15 +543,13 @@
             return CompletableFuture.completedFuture(false);
         }
 
-        processCallSequencing(liveCall, emergencyCall);
-        if (ringingCall != null) {
-            processCallSequencing(ringingCall, liveCall);
-        }
+        boolean isSequencingRequiredRingingAndLive = ringingCall != null
+                && !arePhoneAccountsSame(ringingCall, liveCall);
         if (liveCall.getState() == CallState.AUDIO_PROCESSING) {
             emergencyCall.getAnalytics().setCallIsAdditional(true);
             liveCall.getAnalytics().setCallIsInterrupted(true);
             final String disconnectReason = "disconnecting audio processing call for emergency";
-            if (ringingCallFuture != null && isProcessingCallSequencing()) {
+            if (ringingCallFuture != null && isSequencingRequiredRingingAndLive) {
                 return ringingCallFuture.thenComposeAsync((result) -> {
                     if (result) {
                         Log.i(this, "makeRoomForOutgoingEmergencyCall: Request to disconnect "
@@ -566,7 +586,7 @@
             // easier to do, rather than disconnect a held call.
             final String disconnectReason = "disconnecting to make room for emergency call "
                     + emergencyCall.getId();
-            if (ringingCallFuture != null && isProcessingCallSequencing()) {
+            if (ringingCallFuture != null && isSequencingRequiredRingingAndLive) {
                 return ringingCallFuture.thenComposeAsync((result) -> {
                     if (result) {
                         Log.i(this, "makeRoomForOutgoingEmergencyCall: Request to disconnect "
@@ -605,15 +625,15 @@
         // will not be that one and we do not want multiple PhoneAccounts active during an
         // emergency call if possible. Disconnect the active call in favor of the emergency call
         // instead of trying to hold.
-        if (liveCall.getTargetPhoneAccount() != null) {
+        if (liveCallPhoneAccount != null) {
             PhoneAccount pa = mCallsManager.getPhoneAccountRegistrar().getPhoneAccountUnchecked(
-                    liveCall.getTargetPhoneAccount());
+                    liveCallPhoneAccount);
             if((pa.getCapabilities() & PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS) == 0) {
                 liveCall.setOverrideDisconnectCauseCode(new DisconnectCause(
                         DisconnectCause.LOCAL, DisconnectCause.REASON_EMERGENCY_CALL_PLACED));
                 final String disconnectReason = "outgoing call does not support emergency calls, "
                         + "disconnecting.";
-                if (ringingCallFuture != null && isProcessingCallSequencing()) {
+                if (ringingCallFuture != null && isSequencingRequiredRingingAndLive) {
                     return ringingCallFuture.thenComposeAsync((result) -> {
                         if (result) {
                             Log.i(this, "makeRoomForOutgoingEmergencyCall: Request to disconnect "
@@ -631,8 +651,6 @@
                 } else {
                     return liveCall.disconnect(disconnectReason);
                 }
-            } else {
-                return CompletableFuture.completedFuture(true);
             }
         }
 
@@ -644,7 +662,8 @@
                 emergencyCall.getTargetPhoneAccount())) {
             Log.i(this, "makeRoomForOutgoingEmergencyCall: phoneAccounts are from same "
                     + "package. Attempting to hold live call before placing emergency call.");
-            return maybeHoldLiveCallForEmergency(ringingCallFuture, liveCall, emergencyCall,
+            return maybeHoldLiveCallForEmergency(ringingCallFuture,
+                    isSequencingRequiredRingingAndLive, liveCall, emergencyCall,
                     shouldHoldForEmergencyCall(liveCallPhoneAccount) /* shouldHoldForEmergency */);
         } else if (emergencyCall.getTargetPhoneAccount() == null) {
             // Without a phone account, we can't say reliably that the call will fail.
@@ -659,8 +678,9 @@
         // Hold the live call if possible before attempting the new outgoing emergency call.
         if (mCallsManager.canHold(liveCall)) {
             Log.i(this, "makeRoomForOutgoingEmergencyCall: holding live call.");
-            return maybeHoldLiveCallForEmergency(ringingCallFuture, liveCall, emergencyCall,
-                    true /* shouldHoldForEmergency */);
+            return maybeHoldLiveCallForEmergency(ringingCallFuture,
+                    isSequencingRequiredRingingAndLive, liveCall,
+                    emergencyCall, true /* shouldHoldForEmergency */);
         }
 
         // The live call cannot be held so we're out of luck here.  There's no room.
@@ -668,15 +688,150 @@
         return CompletableFuture.completedFuture(false);
     }
 
+    /**
+     * This function tries to make room for the new outgoing call via call sequencing. The
+     * resulting future is an indication of whether room was able to be made for the call if
+     * needed.
+     * @param call The outgoing call to make room for.
+     * @return The {@code CompletableFuture} indicating the result of whether room was able to be
+     *         made for the outgoing call.
+     */
+    private CompletableFuture<Boolean> makeRoomForOutgoingCall(Call call) {
+        // For the purely managed CS cases, check if there's a ringing call, in which case we will
+        // disallow the outgoing call.
+        if (!call.isSelfManaged() && mCallsManager.hasManagedRingingOrSimulatedRingingCall()) {
+            showErrorDialogForOutgoingDuringRingingCall(call);
+            return CompletableFuture.completedFuture(false);
+        }
+        // Already room!
+        if (!mCallsManager.hasMaximumLiveCalls(call)) {
+            return CompletableFuture.completedFuture(true);
+        }
+
+        // NOTE: If the amount of live calls changes beyond 1, this logic will probably
+        // have to change.
+        Call liveCall = mCallsManager.getFirstCallWithLiveState();
+        Log.i(this, "makeRoomForOutgoingCall call = " + call + " livecall = " +
+                liveCall);
+
+        if (call == liveCall) {
+            // If the call is already the foreground call, then we are golden.
+            // This can happen after the user selects an account in the SELECT_PHONE_ACCOUNT
+            // state since the call was already populated into the list.
+            return CompletableFuture.completedFuture(true);
+        }
+
+        // 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())) {
+            if (mFeatureFlags.telecomMetricsSupport()) {
+                mMetricsController.getErrorStats().log(ErrorStats.SUB_CALL_MANAGER,
+                        ErrorStats.ERROR_STUCK_CONNECTING);
+            }
+            mAnomalyReporter.reportAnomaly(LIVE_CALL_STUCK_CONNECTING_ERROR_UUID,
+                    LIVE_CALL_STUCK_CONNECTING_ERROR_MSG);
+            return liveCall.disconnect("Force disconnect CONNECTING call.");
+        }
+
+        if (mCallsManager.hasMaximumOutgoingCalls(call)) {
+            Call outgoingCall = mCallsManager.getFirstCallWithState(OUTGOING_CALL_STATES);
+            if (outgoingCall.getState() == CallState.SELECT_PHONE_ACCOUNT) {
+                // If there is an orphaned call in the {@link CallState#SELECT_PHONE_ACCOUNT}
+                // state, just disconnect it since the user has explicitly started a new call.
+                call.getAnalytics().setCallIsAdditional(true);
+                outgoingCall.getAnalytics().setCallIsInterrupted(true);
+                return outgoingCall.disconnect(
+                        "Disconnecting call in SELECT_PHONE_ACCOUNT in favor of new "
+                                + "outgoing call.");
+            }
+            showErrorDialogForMaxOutgoingCall(call);
+            return CompletableFuture.completedFuture(false);
+        }
+
+        // Early check to see if we already have a held call + live call. It's possible if a device
+        // switches to DSDS with two ongoing calls for the phone account to be null in which case
+        // we will return true from this method and report a different failure cause instead.
+        if (mCallsManager.hasMaximumManagedHoldingCalls(call) && !mCallsManager.canHold(liveCall)) {
+            showErrorDialogForMaxOutgoingCall(call);
+            return CompletableFuture.completedFuture(false);
+        }
+
+        // Self-Managed + Transactional calls require Telecom to manage calls in the same
+        // PhoneAccount, whereas managed calls require the ConnectionService to manage calls in the
+        // same PhoneAccount for legacy reasons (Telephony).
+        if (arePhoneAccountsSame(call, liveCall) && !call.isSelfManaged()) {
+            Log.i(this, "makeRoomForOutgoingCall: allowing managed CS to handle "
+                    + "calls from the same self-managed account");
+            return CompletableFuture.completedFuture(true);
+        } else if (call.getTargetPhoneAccount() == null) {
+            Log.i(this, "makeRoomForOutgoingCall: no PA specified, allowing");
+            // Without a phone account, we can't say reliably that the call will fail.
+            // If the user chooses the same phone account as the live call, then it's
+            // still possible that the call can be made (like with CDMA calls not supporting
+            // hold but they still support adding a call by going immediately into conference
+            // mode). Return true here and we'll run this code again after user chooses an
+            // account.
+            return CompletableFuture.completedFuture(true);
+        }
+
+        // Try to hold the live call before attempting the new outgoing call.
+        if (mCallsManager.canHold(liveCall)) {
+            Log.i(this, "makeRoomForOutgoingCall: holding live call.");
+            call.getAnalytics().setCallIsAdditional(true);
+            liveCall.getAnalytics().setCallIsInterrupted(true);
+            return liveCall.hold("calling " + call.getId());
+        }
+
+        // The live call cannot be held so we're out of luck here.  There's no room.
+        showErrorDialogForCannotHoldCall(call, true);
+        return CompletableFuture.completedFuture(false);
+    }
+
+    /**
+     * Processes the request from the app to disconnect a call. This is done via call sequencing
+     * so that Telecom properly cleans up the call locally provided that the call has been
+     * properly disconnected on the connection side.
+     * @param call The call to disconnect.
+     * @param previousState The previous state of the call before disconnecting.
+     */
+    public void disconnectCall(Call call, int previousState) {
+        CompletableFuture<Boolean> disconnectFuture = call.disconnect();
+        disconnectFuture.thenComposeAsync((result) -> {
+            if (result) {
+                Log.i(this, "disconnectCall: Disconnect call transaction succeeded. "
+                        + "Processing associated cleanup.");
+                mCallsManager.processDisconnectCallAndCleanup(call, previousState);
+            } else {
+                Log.i(this, "disconnectCall: Disconnect call transaction failed. "
+                        + "Aborting associated cleanup.");
+            }
+            return CompletableFuture.completedFuture(false);
+        }, new LoggedHandlerExecutor(mHandler, "CSC.dC",
+                mCallsManager.getLock()));
+    }
+
+    /* HELPERS */
+
+    /* makeRoomForOutgoingEmergencyCall helpers */
+
     private CompletableFuture<Boolean> maybeHoldLiveCallForEmergency(
-            CompletableFuture<Boolean> ringingCallFuture, Call liveCall, Call emergencyCall,
-            boolean shouldHoldForEmergency) {
+            CompletableFuture<Boolean> ringingCallFuture, boolean isSequencingRequired,
+            Call liveCall, Call emergencyCall, boolean shouldHoldForEmergency) {
         emergencyCall.getAnalytics().setCallIsAdditional(true);
         liveCall.getAnalytics().setCallIsInterrupted(true);
         final String holdReason = "calling " + emergencyCall.getId();
         CompletableFuture<Boolean> holdResultFuture = CompletableFuture.completedFuture(false);
         if (shouldHoldForEmergency) {
-            if (ringingCallFuture != null && isProcessingCallSequencing()) {
+            if (ringingCallFuture != null && isSequencingRequired) {
                 holdResultFuture = ringingCallFuture.thenComposeAsync((result) -> {
                     if (result) {
                         Log.i(this, "makeRoomForOutgoingEmergencyCall: Request to disconnect "
@@ -690,8 +845,7 @@
                 }, new LoggedHandlerExecutor(mHandler, "CSC.mRFOEC",
                         mCallsManager.getLock()));
             } else {
-                emergencyCall.increaseHeldByThisCallCount();
-                return liveCall.hold(holdReason);
+                holdResultFuture = liveCall.hold(holdReason);
             }
         }
         return holdResultFuture.thenComposeAsync((result) -> {
@@ -719,134 +873,11 @@
                 CarrierConfigManager.KEY_ALLOW_HOLD_CALL_DURING_EMERGENCY_BOOL, true);
     }
 
-    /**
-     * This function tries to make room for the new outgoing call via call sequencing. The
-     * resulting future is an indication of whether room was able to be made for the call if
-     * needed.
-     * @param call The outgoing call to make room for.
-     * @return The {@code CompletableFuture} indicating the result of whether room was able to be
-     *         made for the outgoing call.
-     */
-    private CompletableFuture<Boolean> makeRoomForOutgoingCall(Call call) {
-        // Already room!
-        if (!mCallsManager.hasMaximumLiveCalls(call)) {
-            return CompletableFuture.completedFuture(true);
+    @VisibleForTesting
+    public boolean arePhoneAccountsSame(Call call1, Call call2) {
+        if (call1 == null || call2 == null) {
+            return false;
         }
-
-        // NOTE: If the amount of live calls changes beyond 1, this logic will probably
-        // have to change.
-        Call liveCall = mCallsManager.getFirstCallWithLiveState();
-        Log.i(this, "makeRoomForOutgoingCall call = " + call + " livecall = " +
-                liveCall);
-
-        if (call == liveCall) {
-            // If the call is already the foreground call, then we are golden.
-            // This can happen after the user selects an account in the SELECT_PHONE_ACCOUNT
-            // state since the call was already populated into the list.
-            return CompletableFuture.completedFuture(true);
-        }
-
-        CompletableFuture<Boolean> disconnectFuture = mCallsManager
-                .maybeDisconnectExistingCallForNewOutgoingCall(call, liveCall);
-        if (disconnectFuture != null) {
-            return disconnectFuture;
-        }
-
-        // TODO: Remove once b/23035408 has been corrected.
-        // If the live call is a conference, it will not have a target phone account set.  This
-        // means the check to see if the live call has the same target phone account as the new
-        // call will not cause us to bail early.  As a result, we'll end up holding the
-        // ongoing conference call.  However, the ConnectionService is already doing that.  This
-        // has caused problems with some carriers.  As a workaround until b/23035408 is
-        // corrected, we will try and get the target phone account for one of the conference's
-        // children and use that instead.
-        PhoneAccountHandle liveCallPhoneAccount = liveCall.getTargetPhoneAccount();
-        if (liveCallPhoneAccount == null && liveCall.isConference() &&
-                !liveCall.getChildCalls().isEmpty()) {
-            liveCallPhoneAccount = mCallsManager.getFirstChildPhoneAccount(liveCall);
-            Log.i(this, "makeRoomForOutgoingCall: using child call PhoneAccount = " +
-                    liveCallPhoneAccount);
-        }
-
-        if (call.getTargetPhoneAccount() == null) {
-            // Without a phone account, we can't say reliably that the call will fail.
-            // If the user chooses the same phone account as the live call, then it's
-            // still possible that the call can be made (like with CDMA calls not supporting
-            // hold but they still support adding a call by going immediately into conference
-            // mode). Return true here and we'll run this code again after user chooses an
-            // account.
-            return CompletableFuture.completedFuture(true);
-        }
-
-        // Try to hold the live call before attempting the new outgoing call.
-        if (mCallsManager.canHold(liveCall)) {
-            Log.i(this, "makeRoomForOutgoingCall: holding live call.");
-            call.getAnalytics().setCallIsAdditional(true);
-            liveCall.getAnalytics().setCallIsInterrupted(true);
-            return liveCall.hold("calling " + call.getId());
-        }
-
-        // The live call cannot be held so we're out of luck here.  There's no room.
-        int stringId;
-        String reason;
-        if (mCallsManager.hasMaximumManagedHoldingCalls(call)) {
-            call.setStartFailCause(CallFailureCause.MAX_OUTGOING_CALLS);
-            stringId = R.string.callFailed_too_many_calls;
-            reason = " there are two calls already in progress. Disconnect one of the calls "
-                    + "or merge the calls.";
-        } else {
-            call.setStartFailCause(CallFailureCause.CANNOT_HOLD_CALL);
-            stringId = R.string.callFailed_unholdable_call;
-            reason = " unable to hold live call. Disconnect the unholdable call.";
-        }
-        showErrorDialogForRestrictedOutgoingCall(mContext, stringId, TAG, reason);
-        return CompletableFuture.completedFuture(false);
-    }
-
-    /**
-     * Processes the request from the app to disconnect a call. This is done via call sequencing
-     * so that Telecom properly cleans up the call locally provided that the call has been
-     * properly disconnected on the connection side.
-     * @param call The call to disconnect.
-     * @param previousState The previous state of the call before disconnecting.
-     */
-    public void disconnectCall(Call call, int previousState) {
-        CompletableFuture<Boolean> disconnectFuture = call.disconnect();
-        disconnectFuture.thenComposeAsync((result) -> {
-            if (result) {
-                Log.i(this, "disconnectCall: Disconnect call transaction succeeded. "
-                        + "Processing associated cleanup.");
-                mCallsManager.processDisconnectCallAndCleanup(call, previousState);
-            } else {
-                Log.i(this, "disconnectCall: Disconnect call transaction failed. "
-                        + "Aborting associated cleanup.");
-            }
-            return CompletableFuture.completedFuture(false);
-        }, new LoggedHandlerExecutor(mHandler, "CSC.dC",
-                mCallsManager.getLock()));
-    }
-
-    private void resetProcessingCallSequencing() {
-        setProcessingCallSequencing(false);
-    }
-
-    private void setProcessingCallSequencing(boolean processingCallSequencing) {
-        mProcessingCallSequencing = processingCallSequencing;
-    }
-
-    /**
-     * Checks if the 2 calls provided are from the same source and sets the
-     * mProcessingCallSequencing field if they aren't in order to signal that sequencing is
-     * required to verify the call state changes.
-     */
-    private void processCallSequencing(@NonNull Call call1, @NonNull Call call2) {
-        boolean areCallsFromSamePhoneAccount = arePhoneAccountsSame(call1, call2);
-        if (!areCallsFromSamePhoneAccount) {
-            setProcessingCallSequencing(true);
-        }
-    }
-
-    private boolean arePhoneAccountsSame(@NonNull Call call1, @NonNull Call call2) {
         return Objects.equals(call1.getTargetPhoneAccount(), call2.getTargetPhoneAccount());
     }
 
@@ -858,14 +889,72 @@
      * this into account and request to hold regardless.
      */
     @VisibleForTesting
-    public boolean canSwap(Call callToBeHeld, Call callToUnhold) {
+    private boolean canSwap(Call callToBeHeld, Call callToUnhold) {
         return callToBeHeld.can(Connection.CAPABILITY_SUPPORT_HOLD)
                 && callToBeHeld.getState() != CallState.DIALING
                 && callToUnhold.getState() == CallState.ON_HOLD;
     }
 
-    public boolean isProcessingCallSequencing() {
-        return mProcessingCallSequencing;
+    /**
+     * Generic helper to log the result of the {@link CompletableFuture} containing the transactions
+     * that are being processed in the context of call sequencing.
+     * @param future The {@link CompletableFuture} encompassing the transaction that's being
+     *               computed.
+     * @param methodName The method name to describe the type of transaction being processed.
+     * @param sessionName The session name to identify the log.
+     * @param successMsg The message to be logged if the transaction succeeds.
+     * @param failureMsg The message to be logged if the transaction fails.
+     */
+    public void logFutureResultTransaction(CompletableFuture<Boolean> future, String methodName,
+            String sessionName, String successMsg, String failureMsg) {
+        future.thenApplyAsync((result) -> {
+            String msg = methodName + ": " + (result ? successMsg : failureMsg);
+            Log.i(this, msg);
+            return CompletableFuture.completedFuture(result);
+        }, new LoggedHandlerExecutor(mHandler, sessionName, mCallsManager.getLock()));
+    }
+
+    public boolean hasMmiCodeRestriction(Call call) {
+        if (mCallsManager.getNumCallsWithStateWithoutHandle(
+                CALL_FILTER_ALL, call, call.getTargetPhoneAccount(), ONGOING_CALL_STATES) > 0) {
+            // Set disconnect cause so that error will be printed out when call is disconnected.
+            CharSequence msg = mContext.getText(R.string.callFailed_reject_mmi);
+            call.setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.ERROR, msg, msg,
+                    "Rejected MMI code due to an ongoing call on another phone account."));
+            return true;
+        }
+        return false;
+    }
+
+    private void showErrorDialogForMaxOutgoingCall(Call call) {
+        int resourceId = R.string.callFailed_too_many_calls;
+        String reason = " there are two calls already in progress. Disconnect one of the calls "
+                + "or merge the calls.";
+        showErrorDialogForFailedCall(call, CallFailureCause.MAX_OUTGOING_CALLS, resourceId, reason);
+    }
+
+    private void showErrorDialogForOutgoingDuringRingingCall(Call call) {
+        int resourceId = R.string.callFailed_already_ringing;
+        String reason = " can't place outgoing call with an unanswered incoming call.";
+        showErrorDialogForFailedCall(call, null, resourceId, reason);
+    }
+
+    private void showErrorDialogForCannotHoldCall(Call call, boolean setCallFailure) {
+        CallFailureCause cause = null;
+        if (setCallFailure) {
+            cause = CallFailureCause.CANNOT_HOLD_CALL;
+        }
+        int resourceId = R.string.callFailed_unholdable_call;
+        String reason = " unable to hold live call. Disconnect the unholdable call.";
+        showErrorDialogForFailedCall(call, cause, resourceId, reason);
+    }
+
+    private void showErrorDialogForFailedCall(Call call, CallFailureCause cause, int resourceId,
+            String reason) {
+        if (cause != null) {
+            call.setStartFailCause(cause);
+        }
+        showErrorDialogForRestrictedOutgoingCall(mContext, resourceId, TAG, reason);
     }
 
     public Handler getHandler() {
diff --git a/src/com/android/server/telecom/callsequencing/CallsManagerCallSequencingAdapter.java b/src/com/android/server/telecom/callsequencing/CallsManagerCallSequencingAdapter.java
index df0837d..ae2905e 100644
--- a/src/com/android/server/telecom/callsequencing/CallsManagerCallSequencingAdapter.java
+++ b/src/com/android/server/telecom/callsequencing/CallsManagerCallSequencingAdapter.java
@@ -16,18 +16,26 @@
 
 package com.android.server.telecom.callsequencing;
 
+import static com.android.server.telecom.CallsManager.CALL_FILTER_ALL;
+import static com.android.server.telecom.CallsManager.ONGOING_CALL_STATES;
+
+import android.content.Context;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.OutcomeReceiver;
 import android.telecom.CallAttributes;
 import android.telecom.CallException;
+import android.telecom.Connection;
+import android.telecom.DisconnectCause;
 import android.telecom.Log;
 
 import com.android.server.telecom.Call;
+import com.android.server.telecom.CallAudioManager;
+import com.android.server.telecom.CallState;
 import com.android.server.telecom.CallsManager;
-import com.android.server.telecom.LoggedHandlerExecutor;
 import com.android.server.telecom.callsequencing.voip.OutgoingCallTransaction;
 import com.android.server.telecom.flags.FeatureFlags;
+import com.android.server.telecom.R;
 
 import java.util.concurrent.CompletableFuture;
 
@@ -38,43 +46,26 @@
 public class CallsManagerCallSequencingAdapter {
 
     private final CallsManager mCallsManager;
+    private final Context mContext;
     private final CallSequencingController mSequencingController;
+    private final CallAudioManager mCallAudioManager;
     private final Handler mHandler;
     private final FeatureFlags mFeatureFlags;
     private final boolean mIsCallSequencingEnabled;
 
-    public CallsManagerCallSequencingAdapter(CallsManager callsManager,
-            CallSequencingController sequencingController,
+    public CallsManagerCallSequencingAdapter(CallsManager callsManager, Context context,
+            CallSequencingController sequencingController, CallAudioManager callAudioManager,
             FeatureFlags featureFlags) {
         mCallsManager = callsManager;
+        mContext = context;
         mSequencingController = sequencingController;
+        mCallAudioManager = callAudioManager;
         mHandler = sequencingController.getHandler();
         mFeatureFlags = featureFlags;
         mIsCallSequencingEnabled = featureFlags.enableCallSequencing();
     }
 
     /**
-     * Helps create the transaction representing the outgoing transactional call. For outgoing
-     * calls, there can be more than one transaction that will need to complete when
-     * mIsCallSequencingEnabled is true. Otherwise, rely on the old behavior of creating an
-     * {@link OutgoingCallTransaction}.
-     * @param callAttributes The call attributes associated with the call.
-     * @param extras The extras that are associated with the call.
-     * @param callingPackage The calling package representing where the request was invoked from.
-     * @return The {@link CompletableFuture<CallTransaction>} that encompasses the request to
-     *         place/receive the transactional call.
-     */
-    public CompletableFuture<CallTransaction> createTransactionalOutgoingCall(String callId,
-            CallAttributes callAttributes, Bundle extras, String callingPackage) {
-        return mIsCallSequencingEnabled
-                ? mSequencingController.createTransactionalOutgoingCall(callId,
-                        callAttributes, extras, callingPackage)
-                : CompletableFuture.completedFuture(new OutgoingCallTransaction(callId,
-                        mCallsManager.getContext(), callAttributes, mCallsManager, extras,
-                        mFeatureFlags));
-    }
-
-    /**
      * Conditionally try to answer the call depending on whether call sequencing
      * (mIsCallSequencingEnabled) is enabled.
      * @param incomingCall The incoming call that should be answered.
@@ -109,10 +100,8 @@
     public void holdCall(Call call) {
         // Sequencing already taken care of for CSW/TSW in Call class.
         CompletableFuture<Boolean> holdFuture = call.hold();
-        if (mIsCallSequencingEnabled) {
-            logFutureResultTransaction(holdFuture, "holdCall", "CMCSA.hC",
-                    "hold call transaction succeeded.", "hold call transaction failed.");
-        }
+        maybeLogFutureResultTransaction(holdFuture, "holdCall", "CMCSA.hC",
+                "hold call transaction succeeded.", "hold call transaction failed.");
     }
 
     /**
@@ -167,22 +156,115 @@
     }
 
     /**
-     * Attempts to hold the active call for transactional call cases with call sequencing support
-     * if mIsCallSequencingEnabled is true.
+     * Helps create the transaction representing the outgoing transactional call. For outgoing
+     * calls, there can be more than one transaction that will need to complete when
+     * mIsCallSequencingEnabled is true. Otherwise, rely on the old behavior of creating an
+     * {@link OutgoingCallTransaction}.
+     * @param callAttributes The call attributes associated with the call.
+     * @param extras The extras that are associated with the call.
+     * @param callingPackage The calling package representing where the request was invoked from.
+     * @return The {@link CompletableFuture<CallTransaction>} that encompasses the request to
+     *         place/receive the transactional call.
+     */
+    public CompletableFuture<CallTransaction> createTransactionalOutgoingCall(String callId,
+            CallAttributes callAttributes, Bundle extras, String callingPackage) {
+        return mIsCallSequencingEnabled
+                ? mSequencingController.createTransactionalOutgoingCall(callId,
+                callAttributes, extras, callingPackage)
+                : CompletableFuture.completedFuture(new OutgoingCallTransaction(callId,
+                        mCallsManager.getContext(), callAttributes, mCallsManager, extras,
+                        mFeatureFlags));
+    }
+
+    /**
+     * attempt to hold or swap the current active call in favor of a new call request. The
+     * OutcomeReceiver will return onResult if the current active call is held or disconnected.
+     * Otherwise, the OutcomeReceiver will fail.
      * @param newCall The new (transactional) call that's waiting to go active.
-     * @param activeCall The currently active call.
-     * @param callback The callback to report the result of the aforementioned hold transaction.
-     * @return {@code CompletableFuture} indicating the result of holding the active call.
+     * @param isCallControlRequest Indication of whether this is a call control request.
+     * @param callback The callback to report the result of the aforementioned hold
+     *      transaction.
      */
     public void transactionHoldPotentialActiveCallForNewCall(Call newCall,
-            Call activeCall, OutcomeReceiver<Boolean, CallException> callback) {
-        if (mIsCallSequencingEnabled) {
-            mSequencingController.transactionHoldPotentialActiveCallForNewCallSequencing(
-                    newCall, callback);
-        } else {
-            mCallsManager.transactionHoldPotentialActiveCallForNewCallOld(newCall,
-                    activeCall, callback);
+            boolean isCallControlRequest, OutcomeReceiver<Boolean, CallException> callback) {
+        String mTag = "transactionHoldPotentialActiveCallForNewCall: ";
+        Call activeCall = (Call) mCallsManager.getConnectionServiceFocusManager()
+                .getCurrentFocusCall();
+        Log.i(this, mTag + "newCall=[%s], activeCall=[%s]", newCall, activeCall);
+
+        if (activeCall == null || activeCall == newCall) {
+            Log.i(this, mTag + "no need to hold activeCall");
+            callback.onResult(true);
+            return;
         }
+
+        if (mFeatureFlags.transactionalHoldDisconnectsUnholdable()) {
+            // prevent bad actors from disconnecting the activeCall. Instead, clients will need to
+            // notify the user that they need to disconnect the ongoing call before making the
+            // new call ACTIVE.
+            if (isCallControlRequest
+                    && !mCallsManager.canHoldOrSwapActiveCall(activeCall, newCall)) {
+                Log.i(this, mTag + "CallControlRequest exit");
+                callback.onError(new CallException("activeCall is NOT holdable or swappable, please"
+                        + " request the user disconnect the call.",
+                        CallException.CODE_CANNOT_HOLD_CURRENT_ACTIVE_CALL));
+                return;
+            }
+
+            if (mIsCallSequencingEnabled) {
+                mSequencingController.transactionHoldPotentialActiveCallForNewCallSequencing(
+                        newCall, callback);
+            } else {
+                // The code path without sequencing but where transactionalHoldDisconnectsUnholdable
+                // flag is enabled.
+                mCallsManager.transactionHoldPotentialActiveCallForNewCallOld(newCall,
+                        activeCall, callback);
+            }
+        } else {
+            // The unflagged path (aka original code with no flags).
+            mCallsManager.transactionHoldPotentialActiveCallForNewCallUnflagged(activeCall,
+                    newCall, callback);
+        }
+    }
+
+    /**
+     * Attempts to move the held call to the foreground in cases where we need to auto-unhold the
+     * call.
+     */
+    public void maybeMoveHeldCallToForeground(Call removedCall, boolean isLocallyDisconnecting) {
+        CompletableFuture<Boolean> unholdForegroundCallFuture = null;
+        Call foregroundCall = mCallAudioManager.getPossiblyHeldForegroundCall();
+        if (isLocallyDisconnecting) {
+            boolean isDisconnectingChildCall = removedCall.isDisconnectingChildCall();
+            Log.v(this, "maybeMoveHeldCallToForeground: isDisconnectingChildCall = "
+                    + isDisconnectingChildCall + "call -> %s", removedCall);
+            // 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
+                    && CallsManager.areFromSameSource(foregroundCall, removedCall)) {
+
+                unholdForegroundCallFuture = foregroundCall.unhold();
+            }
+        } 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, "maybeMoveHeldCallToForeground: Auto-unholding held foreground call (call "
+                    + "doesn't support hold)");
+            unholdForegroundCallFuture = foregroundCall.unhold();
+        }
+        maybeLogFutureResultTransaction(unholdForegroundCallFuture,
+                "maybeMoveHeldCallToForeground", "CM.mMHCTF",
+                "Successfully unheld the foreground call.",
+                "Failed to unhold the foreground call.");
     }
 
     /**
@@ -195,14 +277,24 @@
      * @param successMsg The message to be logged if the transaction succeeds.
      * @param failureMsg The message to be logged if the transaction fails.
      */
-    public void logFutureResultTransaction(CompletableFuture<Boolean> future, String methodName,
-            String sessionName, String successMsg, String failureMsg) {
-        future.thenApplyAsync((result) -> {
-            StringBuilder msg = new StringBuilder(methodName).append(": ");
-            msg.append(result ? successMsg : failureMsg);
-            Log.i(this, String.valueOf(msg));
-            return CompletableFuture.completedFuture(result);
-        }, new LoggedHandlerExecutor(mHandler, sessionName, mCallsManager.getLock()));
+    public void maybeLogFutureResultTransaction(CompletableFuture<Boolean> future,
+            String methodName, String sessionName, String successMsg, String failureMsg) {
+        if (mFeatureFlags.enableCallSequencing() && future != null) {
+            mSequencingController.logFutureResultTransaction(future, methodName, sessionName,
+                    successMsg, failureMsg);
+        }
+    }
+
+    /**
+     * Tries to see if there are any ongoing calls on another phone account when an MMI code is
+     * detected to determine whether it should be allowed. For DSDA purposes, we will not allow any
+     * MMI codes when there's a call on a different phone account.
+     * @param call The call to ignore and the associated phone account to exclude when getting the
+     *             total call count.
+     * @return {@code true} if the MMI code should be allowed, {@code false} otherwise.
+     */
+    public boolean shouldAllowMmiCode(Call call) {
+        return !mIsCallSequencingEnabled || !mSequencingController.hasMmiCodeRestriction(call);
     }
 
     public Handler getHandler() {
diff --git a/src/com/android/server/telecom/callsequencing/TransactionManager.java b/src/com/android/server/telecom/callsequencing/TransactionManager.java
index 2a6431b..98d54da 100644
--- a/src/com/android/server/telecom/callsequencing/TransactionManager.java
+++ b/src/com/android/server/telecom/callsequencing/TransactionManager.java
@@ -25,6 +25,8 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.telecom.AnomalyReporterAdapter;
+import com.android.server.telecom.flags.FeatureFlags;
 import com.android.server.telecom.flags.Flags;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
@@ -32,6 +34,7 @@
 import java.util.List;
 import java.util.Locale;
 import java.util.Queue;
+import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 
 public class TransactionManager {
@@ -43,6 +46,12 @@
     private final Deque<CallTransaction> mCompletedTransactions;
     private CallTransaction mCurrentTransaction;
     private boolean mProcessingCallSequencing;
+    private AnomalyReporterAdapter mAnomalyReporter;
+    private FeatureFlags mFeatureFlags;
+    public static final UUID TRANSACTION_MANAGER_TIMEOUT_UUID =
+            UUID.fromString("9ccce52e-6694-4357-9e5e-516a9531b062");
+    public static final String TRANSACTION_MANAGER_TIMEOUT_MSG =
+            "TransactionManager hit a timeout while processing a transaction";
 
     public interface TransactionCompleteListener {
         void onTransactionCompleted(CallTransactionResult result, String transactionName);
@@ -67,6 +76,14 @@
         return INSTANCE;
     }
 
+    public void setFeatureFlag(FeatureFlags flag){
+       mFeatureFlags = flag;
+    }
+
+    public void setAnomalyReporter(AnomalyReporterAdapter callAnomalyReporter){
+        mAnomalyReporter = callAnomalyReporter;
+    }
+
     @VisibleForTesting
     public static TransactionManager getTestInstance() {
         return new TransactionManager();
@@ -109,6 +126,12 @@
                     receiver.onError(new CallException(transactionName + " timeout",
                             CODE_OPERATION_TIMED_OUT));
                     transactionCompleteFuture.complete(false);
+                    if (mFeatureFlags != null && mAnomalyReporter != null &&
+                            mFeatureFlags.enableCallExceptionAnomReports()) {
+                        mAnomalyReporter.reportAnomaly(
+                                TRANSACTION_MANAGER_TIMEOUT_UUID,
+                                TRANSACTION_MANAGER_TIMEOUT_MSG);
+                    }
                 } catch (Exception e) {
                     Log.e(TAG, String.format("onTransactionTimeout: Notifying transaction "
                             + " %s resulted in an Exception.", transactionName), e);
diff --git a/src/com/android/server/telecom/callsequencing/TransactionalCallSequencingAdapter.java b/src/com/android/server/telecom/callsequencing/TransactionalCallSequencingAdapter.java
index 570c2cc..4adc8d0 100644
--- a/src/com/android/server/telecom/callsequencing/TransactionalCallSequencingAdapter.java
+++ b/src/com/android/server/telecom/callsequencing/TransactionalCallSequencingAdapter.java
@@ -254,7 +254,7 @@
             }
         };
 
-        mCallsManager.transactionHoldPotentialActiveCallForNewCall(call,
+        mCallsManager.getCallSequencingAdapter().transactionHoldPotentialActiveCallForNewCall(call,
                 isCallControlRequest, maybePerformHoldCallback);
         return createSetActiveFuture[0];
     }
diff --git a/src/com/android/server/telecom/callsequencing/voip/MaybeHoldCallForNewCallTransaction.java b/src/com/android/server/telecom/callsequencing/voip/MaybeHoldCallForNewCallTransaction.java
index 32062b5..cb839dc 100644
--- a/src/com/android/server/telecom/callsequencing/voip/MaybeHoldCallForNewCallTransaction.java
+++ b/src/com/android/server/telecom/callsequencing/voip/MaybeHoldCallForNewCallTransaction.java
@@ -52,8 +52,8 @@
         Log.d(TAG, "processTransaction");
         CompletableFuture<CallTransactionResult> future = new CompletableFuture<>();
 
-        mCallsManager.transactionHoldPotentialActiveCallForNewCall(mCall, mIsCallControlRequest,
-                new OutcomeReceiver<>() {
+        mCallsManager.getCallSequencingAdapter().transactionHoldPotentialActiveCallForNewCall(
+                mCall, mIsCallControlRequest, new OutcomeReceiver<>() {
             @Override
             public void onResult(Boolean result) {
                 Log.d(TAG, "processTransaction: onResult");
diff --git a/src/com/android/server/telecom/callsequencing/voip/OutgoingCallTransactionSequencing.java b/src/com/android/server/telecom/callsequencing/voip/OutgoingCallTransactionSequencing.java
index c38b55d..af6af34 100644
--- a/src/com/android/server/telecom/callsequencing/voip/OutgoingCallTransactionSequencing.java
+++ b/src/com/android/server/telecom/callsequencing/voip/OutgoingCallTransactionSequencing.java
@@ -20,6 +20,7 @@
 
 import android.util.Log;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.LoggedHandlerExecutor;
@@ -35,20 +36,23 @@
     private static final String TAG = OutgoingCallTransactionSequencing.class.getSimpleName();
     private final CompletableFuture<Call> mCallFuture;
     private final CallsManager mCallsManager;
+    private final boolean mCallNotPermitted;
     private FeatureFlags mFeatureFlags;
 
     public OutgoingCallTransactionSequencing(CallsManager callsManager,
-            CompletableFuture<Call> callFuture, FeatureFlags featureFlags) {
+            CompletableFuture<Call> callFuture, boolean callNotPermitted,
+            FeatureFlags featureFlags) {
         super(callsManager.getLock());
         mCallsManager = callsManager;
         mCallFuture = callFuture;
+        mCallNotPermitted = callNotPermitted;
         mFeatureFlags = featureFlags;
     }
 
     @Override
     public CompletionStage<CallTransactionResult> processTransaction(Void v) {
         Log.d(TAG, "processTransaction");
-        if (mCallFuture == null) {
+        if (mCallNotPermitted) {
             return CompletableFuture.completedFuture(
                     new CallTransactionResult(
                             CODE_CALL_NOT_PERMITTED_AT_PRESENT_TIME,
@@ -60,4 +64,9 @@
                         mCallsManager, mFeatureFlags)
                 , new LoggedHandlerExecutor(mHandler, "OCT.pT", null));
     }
+
+    @VisibleForTesting
+    public boolean getCallNotPermitted() {
+        return mCallNotPermitted;
+    }
 }
diff --git a/src/com/android/server/telecom/callsequencing/voip/VoipCallMonitor.java b/src/com/android/server/telecom/callsequencing/voip/VoipCallMonitor.java
index dc770e0..8c74510 100644
--- a/src/com/android/server/telecom/callsequencing/voip/VoipCallMonitor.java
+++ b/src/com/android/server/telecom/callsequencing/voip/VoipCallMonitor.java
@@ -30,9 +30,7 @@
 import android.content.Context;
 import android.content.ServiceConnection;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.IBinder;
-import android.os.Looper;
 import android.os.RemoteException;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.StatusBarNotification;
@@ -53,14 +51,16 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentLinkedQueue;
 
 public class VoipCallMonitor extends CallsManagerListenerBase {
-    private static final long NOTIFICATION_NOT_POSTED_IN_TIME_TIMEOUT = 5000L;
-    private static final long NOTIFICATION_REMOVED_BUT_CALL_IS_STILL_ONGOING_TIMEOUT = 5000L;
+    public static final long NOTIFICATION_NOT_POSTED_IN_TIME_TIMEOUT = 5000L;
+    public static final long NOTIFICATION_REMOVED_BUT_CALL_IS_STILL_ONGOING_TIMEOUT = 5000L;
+    private static final String TAG = VoipCallMonitor.class.getSimpleName();
     private static final String DElIMITER = "#";
     // This list caches calls that are added to the VoipCallMonitor and need an accompanying
     // Call-Style Notification!
-    private final List<Call> mNewCallsMissingCallStyleNotification;
+    private final ConcurrentLinkedQueue<Call> mNewCallsMissingCallStyleNotification;
     private final ConcurrentHashMap<String, Call> mNotificationIdToCall;
     private final ConcurrentHashMap<PhoneAccountHandle, Set<Call>> mAccountHandleToCallMap;
     private final ConcurrentHashMap<PhoneAccountHandle, ServiceConnection> mServices;
@@ -70,11 +70,11 @@
     private final Context mContext;
     private final TelecomSystem.SyncRoot mSyncRoot;
 
-    public VoipCallMonitor(Context context, TelecomSystem.SyncRoot lock) {
+    public VoipCallMonitor(Context context, Handler handler, TelecomSystem.SyncRoot lock) {
         mSyncRoot = lock;
         mContext = context;
-        mHandlerForClass = new Handler(Looper.getMainLooper());
-        mNewCallsMissingCallStyleNotification = new ArrayList<>();
+        mHandlerForClass = handler;
+        mNewCallsMissingCallStyleNotification = new ConcurrentLinkedQueue<>();
         mNotificationIdToCall = new ConcurrentHashMap<>();
         mServices = new ConcurrentHashMap<>();
         mAccountHandleToCallMap = new ConcurrentHashMap<>();
@@ -83,21 +83,18 @@
             @Override
             public void onNotificationPosted(StatusBarNotification sbn) {
                 if (isCallStyleNotification(sbn)) {
-                    Log.i(this, "onNotificationPosted: sbn=[%s]", sbn);
-                    boolean foundCallForNotification = false;
+                    Log.i(TAG, "onNotificationPosted: sbn=[%s]", sbn);
                     // Case 1: Call added to this class (via onCallAdded) BEFORE Call-Style
                     //         Notification is posted by the app (only supported scenario)
-                    // --> remove the newly added call from
-                    //     mNewCallsMissingCallStyleNotification so FGS is not revoked.
-                    for (Call call : new ArrayList<>(mNewCallsMissingCallStyleNotification)) {
+                    Call newCallNoLongerAwaitingNotification = null;
+                    for (Call call : mNewCallsMissingCallStyleNotification) {
                         if (isNotificationForCall(sbn, call)) {
-                            Log.i(this, "onNotificationPosted: found a pending "
+                            Log.i(TAG, "onNotificationPosted: found a pending "
                                     + "call=[%s] for sbn.id=[%s]", call, sbn.getId());
                             mNotificationIdToCall.put(
                                     getNotificationIdToCallKey(sbn),
                                     call);
-                            removeFromNotificationTracking(call);
-                            foundCallForNotification = true;
+                            newCallNoLongerAwaitingNotification = call;
                             break;
                         }
                     }
@@ -105,11 +102,19 @@
                     // --> Currently do not support this
                     // Case 3: Call-Style Notification was updated (ex. incoming -> ongoing)
                     // --> do nothing
-                    if (!foundCallForNotification) {
-                        Log.i(this, "onNotificationPosted: could not find a call for the"
+                    if (newCallNoLongerAwaitingNotification == null) {
+                        Log.i(TAG, "onNotificationPosted: could not find a call for the"
                                 + " sbn.id=[%s]. This could mean the notification posted"
                                 + " BEFORE the call is added (error) or it's an update from"
                                 + " incoming to ongoing (ok).", sbn.getId());
+                    } else {
+                        // --> remove the newly added call from
+                        // mNewCallsMissingCallStyleNotification so FGS is not revoked when the
+                        // timeout is hit in VoipCallMonitor#startMonitoringNotification(...). The
+                        // timeout ensures the voip app posts a call-style notification within
+                        // 5 seconds!
+                        mNewCallsMissingCallStyleNotification
+                                .remove(newCallNoLongerAwaitingNotification);
                     }
                 }
             }
@@ -119,14 +124,17 @@
                 if (!isCallStyleNotification(sbn)) {
                     return;
                 }
-                Log.i(this, "onNotificationRemoved: Call-Style notification=[%s] removed", sbn);
+                Log.i(TAG, "onNotificationRemoved: Call-Style notification=[%s] removed", sbn);
                 Call call = getCallFromStatusBarNotificationId(sbn);
                 if (call != null) {
-                    PhoneAccountHandle handle = getTargetPhoneAccount(call);
                     if (!isCallDisconnected(call)) {
                         mHandlerForClass.postDelayed(() -> {
                             if (isCallStillBeingTracked(call)) {
-                                stopFGSDelegation(call, handle);
+                                Log.w(TAG,
+                                        "onNotificationRemoved: notification has been removed for"
+                                                + " more than 5 seconds but call still ongoing "
+                                                + "c=[%s]", call);
+                                // TODO:: stopFGSDelegation(call, handle) when b/383403913 is fixed
                             }
                         }, NOTIFICATION_REMOVED_BUT_CALL_IS_STILL_ONGOING_TIMEOUT);
                     }
@@ -185,7 +193,7 @@
                     new ComponentName(this.getClass().getPackageName(),
                             this.getClass().getCanonicalName()), ActivityManager.getCurrentUser());
         } catch (RemoteException e) {
-            Log.e(this, e, "Cannot register notification listener");
+            Log.e(TAG, e, "Cannot register notification listener");
         }
     }
 
@@ -193,7 +201,7 @@
         try {
             mNotificationListener.unregisterAsSystemService();
         } catch (RemoteException e) {
-            Log.e(this, e, "Cannot unregister notification listener");
+            Log.e(TAG, e, "Cannot unregister notification listener");
         }
     }
 
@@ -217,11 +225,10 @@
         if (!isTransactional(call) || handle == null) {
             return;
         }
-        removeFromNotificationTracking(call);
         Set<Call> ongoingCalls = mAccountHandleToCallMap
                 .computeIfAbsent(handle, k -> new HashSet<>());
         ongoingCalls.remove(call);
-        Log.d(this, "onCallRemoved: callList.size=[%d]", ongoingCalls.size());
+        Log.d(TAG, "onCallRemoved: callList.size=[%d]", ongoingCalls.size());
         if (ongoingCalls.isEmpty()) {
             stopFGSDelegation(call, handle);
         } else {
@@ -230,7 +237,7 @@
     }
 
     private void maybeStartFGSDelegation(int pid, int uid, PhoneAccountHandle handle, Call call) {
-        Log.i(this, "maybeStartFGSDelegation for call=[%s]", call);
+        Log.i(TAG, "maybeStartFGSDelegation for call=[%s]", call);
         if (mActivityManagerInternal != null) {
             if (mServices.containsKey(handle)) {
                 Log.addEvent(call, LogUtils.Events.ALREADY_HAS_FGS_DELEGATION);
@@ -262,30 +269,38 @@
             try {
                 if (mActivityManagerInternal
                         .startForegroundServiceDelegate(options, fgsConnection)) {
-                    Log.i(this, "maybeStartFGSDelegation: startForegroundServiceDelegate success");
+                    Log.i(TAG, "maybeStartFGSDelegation: startForegroundServiceDelegate success");
                 } else {
                     Log.addEvent(call, LogUtils.Events.GAIN_FGS_DELEGATION_FAILED);
                 }
             } catch (Exception e) {
-                Log.i(this, "startForegroundServiceDelegate failed due to: " + e);
+                Log.i(TAG, "startForegroundServiceDelegate failed due to: " + e);
             }
         }
     }
 
     @VisibleForTesting
     public void stopFGSDelegation(Call call, PhoneAccountHandle handle) {
-        Log.i(this, "stopFGSDelegation of call=[%s]", call);
+        Log.i(TAG, "stopFGSDelegation of call=[%s]", call);
         if (handle == null) {
             return;
         }
+
         // In the event this class is waiting for any new calls to post a notification, cleanup
-        for (Call ongoingCall :  new ArrayList<>(mAccountHandleToCallMap.get(handle))) {
-            removeFromNotificationTracking(ongoingCall);
+        List<Call> toRemove = new ArrayList<>();
+        for (Call callAwaitingNotification : mNewCallsMissingCallStyleNotification) {
+            if (handle.equals(callAwaitingNotification.getTargetPhoneAccount())) {
+                Log.d(TAG, "stopFGSDelegation: removing call from notification tracking c=[%s]",
+                        callAwaitingNotification);
+                toRemove.add(callAwaitingNotification);
+            }
         }
+        mNewCallsMissingCallStyleNotification.removeAll(toRemove);
+
         if (mActivityManagerInternal != null) {
             ServiceConnection fgsConnection = mServices.get(handle);
             if (fgsConnection != null) {
-                Log.i(this, "stopFGSDelegation: requesting stopForegroundServiceDelegate");
+                Log.i(TAG, "stopFGSDelegation: requesting stopForegroundServiceDelegate");
                 mActivityManagerInternal.stopForegroundServiceDelegate(fgsConnection);
             }
         }
@@ -297,17 +312,17 @@
         String callId = getCallId(call);
         // Wait 5 seconds for a CallStyle notification to be posted for the call.
         // If the Call-Style Notification is not posted, FGS delegation needs to be revoked!
-        Log.i(this, "startMonitoringNotification: starting timeout for call.id=[%s]", callId);
-        addToNotificationTracking(call);
+        Log.i(TAG, "startMonitoringNotification: starting timeout for call.id=[%s]", callId);
+        mNewCallsMissingCallStyleNotification.add(call);
         // If no notification is posted, stop foreground service delegation!
         mHandlerForClass.postDelayed(() -> {
-            if (isStillMissingNotification(call)) {
-                Log.i(this, "startMonitoringNotification: A Call-Style-Notification"
+            if (mNewCallsMissingCallStyleNotification.contains(call)) {
+                Log.i(TAG, "startMonitoringNotification: A Call-Style-Notification"
                         + " for voip-call=[%s] hasn't posted in time,"
                         + " stopping delegation for app=[%s].", call, packageName);
                 stopFGSDelegation(call, handle);
             } else {
-                Log.i(this, "startMonitoringNotification: found a call-style"
+                Log.i(TAG, "startMonitoringNotification: found a call-style"
                         + " notification for call.id[%s] at timeout", callId);
             }
         }, NOTIFICATION_NOT_POSTED_IN_TIME_TIMEOUT);
@@ -317,24 +332,6 @@
      * Helpers
      */
 
-    private void addToNotificationTracking(Call call) {
-        synchronized (mNewCallsMissingCallStyleNotification) {
-            mNewCallsMissingCallStyleNotification.add(call);
-        }
-    }
-
-    private boolean isStillMissingNotification(Call call) {
-        synchronized (mNewCallsMissingCallStyleNotification) {
-           return mNewCallsMissingCallStyleNotification.contains(call);
-        }
-    }
-
-    private void removeFromNotificationTracking(Call call) {
-        synchronized (mNewCallsMissingCallStyleNotification) {
-            mNewCallsMissingCallStyleNotification.remove(call);
-        }
-    }
-
     private PhoneAccountHandle getTargetPhoneAccount(Call call) {
         synchronized (mSyncRoot) {
             if (call == null) {
@@ -422,7 +419,17 @@
 
     public boolean hasForegroundServiceDelegation(PhoneAccountHandle handle) {
         boolean hasFgs = mServices.containsKey(handle);
-        Log.i(this, "hasForegroundServiceDelegation: handle=[%s], hasFgs=[%b]", handle, hasFgs);
+        Log.i(TAG, "hasForegroundServiceDelegation: handle=[%s], hasFgs=[%b]", handle, hasFgs);
         return hasFgs;
     }
+
+    @VisibleForTesting
+    public ConcurrentHashMap<PhoneAccountHandle, Set<Call>> getAccountToCallsMapping() {
+        return mAccountHandleToCallMap;
+    }
+
+    @VisibleForTesting
+    public  ConcurrentLinkedQueue<Call> getNewCallsMissingCallStyleNotificationQueue(){
+        return mNewCallsMissingCallStyleNotification;
+    }
 }
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index 4db3e14..2fbdf8b 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -22,6 +22,7 @@
 import android.content.Intent;
 import android.media.IAudioService;
 import android.media.ToneGenerator;
+import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.PowerManager;
 import android.os.ServiceManager;
@@ -111,6 +112,9 @@
                     new NotificationChannelManager();
             notificationChannelManager.createChannels(context);
 
+            HandlerThread handlerThread = new HandlerThread("TelecomSystem");
+            handlerThread.start();
+
             TelecomSystem.setInstance(
                     new TelecomSystem(
                             context,
@@ -242,7 +246,8 @@
                                 }
                             },
                             featureFlags,
-                            new com.android.internal.telephony.flags.FeatureFlagsImpl()));
+                            new com.android.internal.telephony.flags.FeatureFlagsImpl(),
+                            handlerThread.getLooper()));
         }
     }
 
diff --git a/src/com/android/server/telecom/metrics/ApiStats.java b/src/com/android/server/telecom/metrics/ApiStats.java
index 4b23e47..d962276 100644
--- a/src/com/android/server/telecom/metrics/ApiStats.java
+++ b/src/com/android/server/telecom/metrics/ApiStats.java
@@ -169,8 +169,8 @@
     private static final String FILE_NAME = "api_stats";
     private Map<ApiEvent, Integer> mApiStatsMap;
 
-    public ApiStats(@NonNull Context context, @NonNull Looper looper) {
-        super(context, looper);
+    public ApiStats(@NonNull Context context, @NonNull Looper looper, boolean isTestMode) {
+        super(context, looper, isTestMode);
     }
 
     @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
diff --git a/src/com/android/server/telecom/metrics/AudioRouteStats.java b/src/com/android/server/telecom/metrics/AudioRouteStats.java
index 4611b22..a79fdea 100644
--- a/src/com/android/server/telecom/metrics/AudioRouteStats.java
+++ b/src/com/android/server/telecom/metrics/AudioRouteStats.java
@@ -76,8 +76,8 @@
     private Pair<AudioRouteStatsKey, long[]> mCur;
     private boolean mIsOngoing;
 
-    public AudioRouteStats(@NonNull Context context, @NonNull Looper looper) {
-        super(context, looper);
+    public AudioRouteStats(@NonNull Context context, @NonNull Looper looper, boolean isTestMode) {
+        super(context, looper, isTestMode);
     }
 
     @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
diff --git a/src/com/android/server/telecom/metrics/CallStats.java b/src/com/android/server/telecom/metrics/CallStats.java
index 8bdeffb..f518557 100644
--- a/src/com/android/server/telecom/metrics/CallStats.java
+++ b/src/com/android/server/telecom/metrics/CallStats.java
@@ -22,6 +22,8 @@
 import static com.android.server.telecom.TelecomStatsLog.CALL_STATS__ACCOUNT_TYPE__ACCOUNT_SIM;
 import static com.android.server.telecom.TelecomStatsLog.CALL_STATS__ACCOUNT_TYPE__ACCOUNT_UNKNOWN;
 import static com.android.server.telecom.TelecomStatsLog.CALL_STATS__ACCOUNT_TYPE__ACCOUNT_VOIP_API;
+import static com.android.server.telecom.TelecomStatsLog.CALL_STATS__ACCOUNT_TYPE__ACCOUNT_NON_TELECOM_VOIP;
+import static com.android.server.telecom.TelecomStatsLog.CALL_STATS__ACCOUNT_TYPE__ACCOUNT_NON_TELECOM_VOIP_WITH_TELECOM_SUPPORT;
 import static com.android.server.telecom.TelecomStatsLog.CALL_STATS__CALL_DIRECTION__DIR_INCOMING;
 import static com.android.server.telecom.TelecomStatsLog.CALL_STATS__CALL_DIRECTION__DIR_OUTGOING;
 import static com.android.server.telecom.TelecomStatsLog.CALL_STATS__CALL_DIRECTION__DIR_UNKNOWN;
@@ -57,8 +59,8 @@
     private Map<CallStatsKey, CallStatsData> mCallStatsMap;
     private boolean mHasMultipleAudioDevices;
 
-    public CallStats(@NonNull Context context, @NonNull Looper looper) {
-        super(context, looper);
+    public CallStats(@NonNull Context context, @NonNull Looper looper, boolean isTestMode) {
+        super(context, looper, isTestMode);
     }
 
     @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
@@ -173,6 +175,28 @@
         });
     }
 
+    /**
+     * Used for logging non-telecom calls that have no associated {@link Call}.  This is inferred
+     * from the {@link com.android.server.telecom.CallAudioWatchdog}.
+     *
+     * @param hasTelecomSupport {@code true} if the app making the non-telecom call has Telecom
+     *                                      support (i.e. has a phone account};
+     *                                      {@code false} otherwise.
+     * @param uid The uid of the app making the call.
+     * @param durationMillis The duration of the call, in millis.
+     */
+    public void onNonTelecomCallEnd(final boolean hasTelecomSupport, final int uid,
+            final long durationMillis) {
+        post(() -> log(CALL_STATS__CALL_DIRECTION__DIR_UNKNOWN,
+                false /* isExternalCall */,
+                false /* isEmergencyCall */,
+                false /* hasMultipleAudioDevices  */,
+                hasTelecomSupport ?
+                        CALL_STATS__ACCOUNT_TYPE__ACCOUNT_NON_TELECOM_VOIP_WITH_TELECOM_SUPPORT :
+                        CALL_STATS__ACCOUNT_TYPE__ACCOUNT_NON_TELECOM_VOIP,
+                uid, (int) durationMillis));
+    }
+
     private int getAccountType(PhoneAccount account) {
         if (account == null) {
             return CALL_STATS__ACCOUNT_TYPE__ACCOUNT_UNKNOWN;
diff --git a/src/com/android/server/telecom/metrics/ErrorStats.java b/src/com/android/server/telecom/metrics/ErrorStats.java
index f334710..7f8ddd7 100644
--- a/src/com/android/server/telecom/metrics/ErrorStats.java
+++ b/src/com/android/server/telecom/metrics/ErrorStats.java
@@ -118,8 +118,8 @@
     private static final String FILE_NAME = "error_stats";
     private Map<ErrorEvent, Integer> mErrorStatsMap;
 
-    public ErrorStats(@NonNull Context context, @NonNull Looper looper) {
-        super(context, looper);
+    public ErrorStats(@NonNull Context context, @NonNull Looper looper, boolean isTestMode) {
+        super(context, looper, isTestMode);
     }
 
     @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
diff --git a/src/com/android/server/telecom/metrics/TelecomMetricsController.java b/src/com/android/server/telecom/metrics/TelecomMetricsController.java
index c642303..23673ca 100644
--- a/src/com/android/server/telecom/metrics/TelecomMetricsController.java
+++ b/src/com/android/server/telecom/metrics/TelecomMetricsController.java
@@ -37,6 +37,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 public class TelecomMetricsController implements StatsManager.StatsPullAtomCallback {
 
@@ -45,6 +46,7 @@
     private final Context mContext;
     private final HandlerThread mHandlerThread;
     private final ConcurrentHashMap<Integer, TelecomPulledAtom> mStats = new ConcurrentHashMap<>();
+    private final AtomicBoolean mIsTestMode = new AtomicBoolean(false);
 
     private TelecomMetricsController(@NonNull Context context,
                                      @NonNull HandlerThread handlerThread) {
@@ -76,7 +78,7 @@
         if (stats == null) {
             long token = Binder.clearCallingIdentity();
             try {
-                stats = new ApiStats(mContext, mHandlerThread.getLooper());
+                stats = new ApiStats(mContext, mHandlerThread.getLooper(), isTestMode());
                 registerAtom(stats.getTag(), stats);
             } finally {
                 Binder.restoreCallingIdentity(token);
@@ -89,7 +91,7 @@
     public AudioRouteStats getAudioRouteStats() {
         AudioRouteStats stats = (AudioRouteStats) mStats.get(CALL_AUDIO_ROUTE_STATS);
         if (stats == null) {
-            stats = new AudioRouteStats(mContext, mHandlerThread.getLooper());
+            stats = new AudioRouteStats(mContext, mHandlerThread.getLooper(), isTestMode());
             registerAtom(stats.getTag(), stats);
         }
         return stats;
@@ -99,7 +101,7 @@
     public CallStats getCallStats() {
         CallStats stats = (CallStats) mStats.get(CALL_STATS);
         if (stats == null) {
-            stats = new CallStats(mContext, mHandlerThread.getLooper());
+            stats = new CallStats(mContext, mHandlerThread.getLooper(), isTestMode());
             registerAtom(stats.getTag(), stats);
         }
         return stats;
@@ -109,7 +111,7 @@
     public ErrorStats getErrorStats() {
         ErrorStats stats = (ErrorStats) mStats.get(TELECOM_ERROR_STATS);
         if (stats == null) {
-            stats = new ErrorStats(mContext, mHandlerThread.getLooper());
+            stats = new ErrorStats(mContext, mHandlerThread.getLooper(), isTestMode());
             registerAtom(stats.getTag(), stats);
         }
         return stats;
@@ -140,14 +142,30 @@
     }
 
     public void destroy() {
+        clearStats();
+        mHandlerThread.quitSafely();
+    }
+
+    public void setTestMode(boolean enabled) {
+        mIsTestMode.set(enabled);
+        clearStats();
+    }
+
+    public boolean isTestMode() {
+        return mIsTestMode.get();
+    }
+
+    private void clearStats() {
         final StatsManager statsManager = mContext.getSystemService(StatsManager.class);
         if (statsManager != null) {
-            mStats.forEach((tag, stat) -> statsManager.clearPullAtomCallback(tag));
+            mStats.forEach((tag, stat) -> {
+                statsManager.clearPullAtomCallback(tag);
+                stat.flush();
+            });
         } else {
             Log.w(TAG, "Unable to clear pulled atoms as StatsManager is null");
         }
 
         mStats.clear();
-        mHandlerThread.quitSafely();
     }
 }
diff --git a/src/com/android/server/telecom/metrics/TelecomPulledAtom.java b/src/com/android/server/telecom/metrics/TelecomPulledAtom.java
index 161eaa8..d60fc77 100644
--- a/src/com/android/server/telecom/metrics/TelecomPulledAtom.java
+++ b/src/com/android/server/telecom/metrics/TelecomPulledAtom.java
@@ -45,23 +45,28 @@
     private static final long MIN_PULL_INTERVAL_MILLIS = 23L * 60 * 60 * 1000;
     private static final int EVENT_SAVE = 1;
     protected final Context mContext;
+    protected final boolean mIsTestMode;
     @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
     public PulledAtoms mPulledAtoms;
     protected long mLastPulledTimestamps;
 
-    protected TelecomPulledAtom(@NonNull Context context, @NonNull Looper looper) {
+    protected TelecomPulledAtom(@NonNull Context context, @NonNull Looper looper,
+                                boolean isTestMode) {
         super(looper);
         mContext = context;
+        mIsTestMode = isTestMode;
         mPulledAtoms = loadAtomsFromFile();
         onLoad();
     }
 
     public synchronized int pull(final List<StatsEvent> data) {
-        long cur = System.currentTimeMillis();
-        if (cur - mLastPulledTimestamps < MIN_PULL_INTERVAL_MILLIS) {
-            return StatsManager.PULL_SKIP;
+        if (!mIsTestMode) {
+            long cur = System.currentTimeMillis();
+            if (cur - mLastPulledTimestamps < MIN_PULL_INTERVAL_MILLIS) {
+                return StatsManager.PULL_SKIP;
+            }
+            mLastPulledTimestamps = cur;
         }
-        mLastPulledTimestamps = cur;
         return onPull(data);
     }
 
@@ -76,21 +81,22 @@
     @VisibleForTesting(otherwise = VisibleForTesting.PROTECTED)
     public abstract void onAggregate();
 
-    public void onFlush() {
+    public void flush() {
         save(0);
     }
 
     protected abstract String getFileName();
 
     private synchronized PulledAtoms loadAtomsFromFile() {
-        try {
-            return
-                    PulledAtoms.parseFrom(
-                            Files.readAllBytes(mContext.getFileStreamPath(getFileName()).toPath()));
-        } catch (NoSuchFileException e) {
-            Log.e(TAG, e, "the atom file not found");
-        } catch (IOException | NullPointerException e) {
-            Log.e(TAG, e, "cannot load/parse the atom file");
+        if (!mIsTestMode) {
+            try {
+                return PulledAtoms.parseFrom(
+                        Files.readAllBytes(mContext.getFileStreamPath(getFileName()).toPath()));
+            } catch (NoSuchFileException e) {
+                Log.e(TAG, e, "the atom file not found");
+            } catch (IOException | NullPointerException e) {
+                Log.e(TAG, e, "cannot load/parse the atom file");
+            }
         }
         return makeNewPulledAtoms();
     }
@@ -100,14 +106,16 @@
     }
 
     private synchronized void onSave() {
-        try (FileOutputStream stream = mContext.openFileOutput(getFileName(),
-                Context.MODE_PRIVATE)) {
-            Log.d(TAG, "save " + getTag());
-            stream.write(PulledAtoms.toByteArray(mPulledAtoms));
-        } catch (IOException e) {
-            Log.e(TAG, e, "cannot save the atom to file");
-        } catch (UnsupportedOperationException e) {
-            Log.e(TAG, e, "cannot open the file");
+        if (!mIsTestMode) {
+            try (FileOutputStream stream = mContext.openFileOutput(getFileName(),
+                    Context.MODE_PRIVATE)) {
+                Log.d(TAG, "save " + getTag());
+                stream.write(PulledAtoms.toByteArray(mPulledAtoms));
+            } catch (IOException e) {
+                Log.e(TAG, e, "cannot save the atom to file");
+            } catch (UnsupportedOperationException e) {
+                Log.e(TAG, e, "cannot open the file");
+            }
         }
     }
 
diff --git a/tests/src/com/android/server/telecom/tests/BasicCallTests.java b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
index 7646c2d..c403c50 100644
--- a/tests/src/com/android/server/telecom/tests/BasicCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
@@ -1282,7 +1282,7 @@
         // Stub intent for call2
         Intent callIntent2 = new Intent();
         Bundle callExtras1 = new Bundle();
-        Icon icon = Icon.createWithContentUri("content://10@media/external/images/media/");
+        Icon icon = Icon.createWithContentUri("content://12@media/external/images/media/");
         // Load StatusHints extra into TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS to be processed
         // as the call extras. This will be leveraged in ConnectionServiceFixture to set the
         // StatusHints for the given connection.
@@ -1315,7 +1315,7 @@
     @Test
     public void testValidateStatusHintsImage_handleCreateConnectionComplete() throws Exception {
         Bundle extras = new Bundle();
-        Icon icon = Icon.createWithContentUri("content://10@media/external/images/media/");
+        Icon icon = Icon.createWithContentUri("content://12@media/external/images/media/");
         // Load the bundle with the test extra in order to simulate an app directly invoking the
         // binder on ConnectionServiceWrapper#handleCreateConnectionComplete.
         StatusHints statusHints = new StatusHints(icon);
@@ -1349,7 +1349,7 @@
                 mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
 
         // Modify existing connection with StatusHints image exploit
-        Icon icon = Icon.createWithContentUri("content://10@media/external/images/media/");
+        Icon icon = Icon.createWithContentUri("content://12@media/external/images/media/");
         StatusHints statusHints = new StatusHints(icon);
         assertNotNull(statusHints.getIcon());
         ConnectionServiceFixture.ConnectionInfo connectionInfo = mConnectionServiceFixtureA
@@ -1384,7 +1384,7 @@
                 mPhoneAccountA0.getAccountHandle(), mConnectionServiceFixtureA);
 
         // Modify existing connection with StatusHints image exploit
-        Icon icon = Icon.createWithContentUri("content://10@media/external/images/media/");
+        Icon icon = Icon.createWithContentUri("content://12@media/external/images/media/");
         StatusHints modifiedStatusHints = new StatusHints(icon);
         assertNotNull(modifiedStatusHints.getIcon());
         ConnectionServiceFixture.ConnectionInfo connectionInfo = mConnectionServiceFixtureA
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
index 1c885c1..4913904 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
@@ -35,6 +35,7 @@
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothStatusCodes;
 import android.content.ContentResolver;
+import android.media.AudioDeviceInfo;
 import android.os.Parcel;
 import android.telecom.Log;
 
@@ -104,6 +105,8 @@
             BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID);
         when(mDeviceManager.connectAudio(anyString(), anyBoolean())).thenReturn(true);
         when(mDeviceManager.isHearingAidSetAsCommunicationDevice()).thenReturn(true);
+        when(mCommunicationDeviceTracker.isAudioDeviceSetForType(
+                eq(AudioDeviceInfo.TYPE_HEARING_AID))).thenReturn(true);
 
         setupConnectedDevices(null, HEARING_AIDS, null, null, HEARING_AIDS, null);
         when(mBluetoothHeadset.getAudioState(nullable(BluetoothDevice.class)))
@@ -130,7 +133,8 @@
             BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID);
         when(mDeviceManager.connectAudio(anyString(), anyBoolean())).thenReturn(true);
         when(mDeviceManager.isHearingAidSetAsCommunicationDevice()).thenReturn(true);
-
+        when(mCommunicationDeviceTracker.isAudioDeviceSetForType(
+                eq(AudioDeviceInfo.TYPE_HEARING_AID))).thenReturn(true);
 
         setupConnectedDevices(null, HEARING_AIDS, null, null, HEARING_AIDS, null);
         when(mBluetoothHeadset.getAudioState(nullable(BluetoothDevice.class)))
@@ -299,7 +303,8 @@
         resetMocks();
         BluetoothRouteManager sm = new BluetoothRouteManager(mContext,
                 new TelecomSystem.SyncRoot() { }, mDeviceManager,
-                mTimeoutsAdapter, mCommunicationDeviceTracker, mFeatureFlags);
+                mTimeoutsAdapter, mCommunicationDeviceTracker, mFeatureFlags,
+                mContext.getMainLooper());
         sm.setListener(mListener);
         sm.setInitialStateForTesting(initialState, initialDevice);
         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java b/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
index c546c3f..004bcd3 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
@@ -420,7 +420,8 @@
                 nullable(ContentResolver.class))).thenReturn(100000L);
         BluetoothRouteManager sm = new BluetoothRouteManager(mContext,
                 new TelecomSystem.SyncRoot() { }, mDeviceManager,
-                mTimeoutsAdapter, mCommunicationDeviceTracker, mFeatureFlags);
+                mTimeoutsAdapter, mCommunicationDeviceTracker, mFeatureFlags,
+                mContext.getMainLooper());
         sm.setListener(mListener);
         sm.setInitialStateForTesting(initialState, initialDevice);
         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
index fa1afbb..19b08c6 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
@@ -463,6 +463,28 @@
 
     @SmallTest
     @Test
+    public void testDefaultSpeakerOnWiredHeadsetDisconnect() {
+        when(mFeatureFlags.defaultSpeakerOnWiredHeadsetDisconnect()).thenReturn(true);
+        mController.initialize();
+        mController.setActive(true);
+        verifyMaybeDefaultSpeakerOnDisconnectWiredHeadset(
+                CallAudioState.ROUTE_SPEAKER /* expectedAudioType */);
+    }
+
+    @SmallTest
+    @Test
+    public void testIgnoreDefaultSpeakerOnWiredHeadsetDisconnect() {
+        when(mFeatureFlags.defaultSpeakerOnWiredHeadsetDisconnect()).thenReturn(true);
+        // Note here that the routing isn't active to represent that we're not in a call. If a wired
+        // headset is disconnected and the last route was speaker, we shouldn't switch back to
+        // speaker when we're not in a call.
+        mController.initialize();
+        verifyMaybeDefaultSpeakerOnDisconnectWiredHeadset(
+                CallAudioState.ROUTE_EARPIECE /* expectedAudioType */);
+    }
+
+    @SmallTest
+    @Test
     public void testConnectAndDisconnectDock() {
         mController.initialize();
         mController.sendMessageWithSessionInfo(CONNECT_DOCK);
@@ -695,7 +717,7 @@
                 new HashSet<>());
         verify(mAudioService, timeout(TEST_TIMEOUT)).setMicrophoneMute(eq(true), anyString(),
                 anyInt(), anyString());
-        verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+        verify(mCallsManager, timeout(TEST_TIMEOUT).atLeastOnce()).onCallAudioStateChanged(
                 any(CallAudioState.class), eq(expectedState));
     }
 
@@ -1332,4 +1354,31 @@
         verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
                 any(CallAudioState.class), eq(expectedState));
     }
+
+    private void verifyMaybeDefaultSpeakerOnDisconnectWiredHeadset(int expectedAudioType) {
+        // Ensure audio is routed to speaker initially
+        mController.sendMessageWithSessionInfo(SPEAKER_ON);
+        CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER, null,
+                new HashSet<>());
+        verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+                any(CallAudioState.class), eq(expectedState));
+
+        // Then simulate wired headset being connected after speaker was initially the audio route
+        mController.sendMessageWithSessionInfo(CONNECT_WIRED_HEADSET);
+        mController.sendMessageWithSessionInfo(SPEAKER_OFF);
+        expectedState = new CallAudioState(false, CallAudioState.ROUTE_WIRED_HEADSET,
+                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_SPEAKER, null,
+                new HashSet<>());
+        verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+                any(CallAudioState.class), eq(expectedState));
+
+        // Verify that we route back into speaker once the wired headset disconnects
+        mController.sendMessageWithSessionInfo(DISCONNECT_WIRED_HEADSET);
+        expectedState = new CallAudioState(false, expectedAudioType,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER, null,
+                new HashSet<>());
+        verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+                any(CallAudioState.class), eq(expectedState));
+    }
 }
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioWatchdogTest.java b/tests/src/com/android/server/telecom/tests/CallAudioWatchdogTest.java
index 9a9b77c..4f988f1 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioWatchdogTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioWatchdogTest.java
@@ -22,7 +22,14 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.annotation.NonNull;
@@ -40,6 +47,8 @@
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallAudioWatchdog;
 import com.android.server.telecom.ClockProxy;
+import com.android.server.telecom.metrics.CallStats;
+import com.android.server.telecom.metrics.TelecomMetricsController;
 
 import org.junit.After;
 import org.junit.Before;
@@ -62,6 +71,7 @@
 public class CallAudioWatchdogTest extends TelecomTestCase {
     private static final String TEST_CALL_ID = "TC@90210";
     private static final int TEST_APP_1_UID = 10001;
+    private static final int TEST_APP_2_UID = 10002;
     private static final PhoneAccountHandle TEST_APP_1_HANDLE = new PhoneAccountHandle(
             new ComponentName("com.app1.package", "class1"), "1");
     private static final ArrayMap<Integer, PhoneAccountHandle> TEST_UID_TO_PHAC = new ArrayMap<>();
@@ -86,15 +96,19 @@
             };
 
     @Mock private ClockProxy mClockProxy;
+    @Mock private TelecomMetricsController mMetricsController;
+    @Mock private CallStats mCallStats;
     private CallAudioWatchdog mCallAudioWatchdog;
 
     @Override
     @Before
     public void setUp() throws Exception {
         super.setUp();
+        when(mMetricsController.getCallStats()).thenReturn(mCallStats);
+        when(mClockProxy.elapsedRealtime()).thenReturn(0L);
         TEST_UID_TO_PHAC.put(TEST_APP_1_UID, TEST_APP_1_HANDLE);
         mCallAudioWatchdog = new CallAudioWatchdog(mComponentContextFixture.getAudioManager(),
-                mPhoneAccountRegistrarProxy, mClockProxy, null /* mHandler */);
+                mPhoneAccountRegistrarProxy, mClockProxy, null /* mHandler */, mMetricsController);
     }
 
     @Override
@@ -177,9 +191,75 @@
 
         when(mComponentContextFixture.getAudioManager().getActiveRecordingConfigurations())
                 .thenReturn(Collections.EMPTY_LIST);
+        when(mClockProxy.elapsedRealtime()).thenReturn(1000L);
         mCallAudioWatchdog.getWatchdogAudioRecordCallack().onRecordingConfigChanged(
                 Collections.EMPTY_LIST);
         assertFalse(mCallAudioWatchdog.getCommunicationSessions().containsKey(TEST_APP_1_UID));
+
+        // Ensure that a call with telecom support but which did not use Telecom gets logged to
+        // metrics as a non-telecom call.
+        verify(mCallStats).onNonTelecomCallEnd(eq(true), eq(TEST_APP_1_UID), eq(1000L));
+    }
+
+    /**
+     * Verifies ability of the audio watchdog to track non-telecom calls where there is no Telecom
+     * integration.
+     */
+    @Test
+    public void testNonTelecomCallMetricsTracking() {
+        var client1Recording = makeAudioRecordingConfiguration(TEST_APP_2_UID, 1);
+        var theRecords = Arrays.asList(client1Recording);
+        when(mComponentContextFixture.getAudioManager().getActiveRecordingConfigurations())
+                .thenReturn(theRecords);
+        mCallAudioWatchdog.getWatchdogAudioRecordCallack().onRecordingConfigChanged(theRecords);
+        assertTrue(mCallAudioWatchdog.getCommunicationSessions().containsKey(TEST_APP_2_UID));
+
+        when(mComponentContextFixture.getAudioManager().getActiveRecordingConfigurations())
+                .thenReturn(Collections.EMPTY_LIST);
+        when(mClockProxy.elapsedRealtime()).thenReturn(1000L);
+        mCallAudioWatchdog.getWatchdogAudioRecordCallack().onRecordingConfigChanged(
+                Collections.EMPTY_LIST);
+        assertFalse(mCallAudioWatchdog.getCommunicationSessions().containsKey(TEST_APP_2_UID));
+
+        // This should log as a non-telecom call with no telecom support.
+        verify(mCallStats).onNonTelecomCallEnd(eq(false), eq(TEST_APP_2_UID), eq(1000L));
+    }
+
+    /**
+     * Verifies that if a call known to Telecom is added, that we don't try to track it in the
+     * non-telecom metrics.
+     */
+    @Test
+    public void testTelecomCallMetricsTracking() {
+        var client1Recording = makeAudioRecordingConfiguration(TEST_APP_1_UID, 1);
+        var theRecords = Arrays.asList(client1Recording);
+        when(mComponentContextFixture.getAudioManager().getActiveRecordingConfigurations())
+                .thenReturn(theRecords);
+        mCallAudioWatchdog.getWatchdogAudioRecordCallack().onRecordingConfigChanged(theRecords);
+        assertTrue(mCallAudioWatchdog.getCommunicationSessions().containsKey(TEST_APP_1_UID));
+
+        Call mockCall = mock(Call.class);
+        when(mockCall.isSelfManaged()).thenReturn(true);
+        when(mockCall.isExternalCall()).thenReturn(false);
+        when(mockCall.getTargetPhoneAccount()).thenReturn(TEST_APP_1_HANDLE);
+        when(mockCall.getId()).thenReturn("90210");
+        mCallAudioWatchdog.onCallAdded(mockCall);
+
+        when(mComponentContextFixture.getAudioManager().getActiveRecordingConfigurations())
+                .thenReturn(Collections.EMPTY_LIST);
+        when(mClockProxy.elapsedRealtime()).thenReturn(1000L);
+        mCallAudioWatchdog.getWatchdogAudioRecordCallack().onRecordingConfigChanged(
+                Collections.EMPTY_LIST);
+        assertTrue(mCallAudioWatchdog.getCommunicationSessions().containsKey(TEST_APP_1_UID));
+
+        mCallAudioWatchdog.onCallRemoved(mockCall);
+        assertFalse(mCallAudioWatchdog.getCommunicationSessions().containsKey(TEST_APP_1_UID));
+
+        // We should not log a non-telecom call.  Note; we are purposely NOT trying to check if a
+        // Telecom call metric is logged here since that is done elsewhere and this unit test is
+        // only testing CallAudioWatchdog in isolation.
+        verify(mCallStats, never()).onNonTelecomCallEnd(anyBoolean(), anyInt(), anyLong());
+
     }
 
     private AudioPlaybackConfiguration makeAudioPlaybackConfiguration(int clientUid,
diff --git a/tests/src/com/android/server/telecom/tests/CallSequencingTests.java b/tests/src/com/android/server/telecom/tests/CallSequencingTests.java
new file mode 100644
index 0000000..64d2d7d
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/CallSequencingTests.java
@@ -0,0 +1,722 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.telecom.tests;
+
+import static com.android.server.telecom.CallsManager.CALL_FILTER_ALL;
+import static com.android.server.telecom.CallsManager.ONGOING_CALL_STATES;
+import static com.android.server.telecom.UserUtil.showErrorDialogForRestrictedOutgoingCall;
+
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.TestCase.fail;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.OutcomeReceiver;
+import android.os.PersistableBundle;
+import android.os.UserHandle;
+import android.telecom.CallAttributes;
+import android.telecom.CallException;
+import android.telecom.Connection;
+import android.telecom.DisconnectCause;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telephony.CarrierConfigManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.server.telecom.Analytics;
+import com.android.server.telecom.AnomalyReporterAdapter;
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.ClockProxy;
+import com.android.server.telecom.ConnectionServiceFocusManager;
+import com.android.server.telecom.PhoneAccountRegistrar;
+import com.android.server.telecom.Timeouts;
+import com.android.server.telecom.callsequencing.CallSequencingController;
+import com.android.server.telecom.callsequencing.CallTransaction;
+import com.android.server.telecom.callsequencing.voip.OutgoingCallTransactionSequencing;
+import com.android.server.telecom.metrics.TelecomMetricsController;
+import com.android.server.telecom.stats.CallFailureCause;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(JUnit4.class)
+public class CallSequencingTests extends TelecomTestCase {
+    private static final long SEQUENCING_TIMEOUT_MS = 2000L;
+    private static final PhoneAccountHandle mHandle1 = new PhoneAccountHandle(
+            new ComponentName("foo", "bar"), "1");
+    private static final PhoneAccountHandle mHandle2 = new PhoneAccountHandle(
+            new ComponentName("bar", "foo"), "2");
+    private static final String TEST_NAME = "Alan Turing";
+    private static final Uri TEST_URI = Uri.fromParts("tel", "abc", "123");
+    private static final String ACTIVE_CALL_ID = "TC@1";
+    private static final String NEW_CALL_ID = "TC@2";
+
+    private CallSequencingController mController;
+    @Mock
+    private CallsManager mCallsManager;
+    @Mock Context mContext;
+    @Mock ClockProxy mClockProxy;
+    @Mock AnomalyReporterAdapter mAnomalyReporter;
+    @Mock Timeouts.Adapter mTimeoutsAdapter;
+    @Mock TelecomMetricsController mMetricsController;
+    @Mock
+    ConnectionServiceFocusManager mConnectionServiceFocusManager;
+    @Mock Call mActiveCall;
+    @Mock Call mHeldCall;
+    @Mock Call mNewCall;
+    @Mock Call mRingingCall;
+
+    @Override
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        when(mFeatureFlags.enableCallSequencing()).thenReturn(true);
+        mController = new CallSequencingController(mCallsManager, mContext, mClockProxy,
+                mAnomalyReporter, mTimeoutsAdapter, mMetricsController, mFeatureFlags);
+
+        when(mActiveCall.getState()).thenReturn(CallState.ACTIVE);
+        when(mRingingCall.getState()).thenReturn(CallState.RINGING);
+        when(mHeldCall.getState()).thenReturn(CallState.ON_HOLD);
+
+        when(mActiveCall.getId()).thenReturn(ACTIVE_CALL_ID);
+        when(mNewCall.getId()).thenReturn(NEW_CALL_ID);
+    }
+
+    @Override
+    @After
+    public void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+
+    @Test
+    @SmallTest
+    public void testTransactionOutgoingCall_CallNotPermitted() {
+        String callingPkg = "testPkg";
+        CallAttributes outgoingCallAttributes = getOutgoingCallAttributes();
+
+        // Outgoing call is not permitted
+        when(mCallsManager.isOutgoingCallPermitted(mHandle1)).thenReturn(false);
+        CompletableFuture<CallTransaction> transactionFuture = mController
+                .createTransactionalOutgoingCall("callId", outgoingCallAttributes,
+                        new Bundle(), callingPkg);
+        OutgoingCallTransactionSequencing transaction = (OutgoingCallTransactionSequencing)
+                transactionFuture.getNow(null);
+        assertNotNull(transaction);
+        assertTrue(transaction.getCallNotPermitted());
+
+        // Call future is null
+        when(mCallsManager.isOutgoingCallPermitted(mHandle1)).thenReturn(true);
+        when(mCallsManager.startOutgoingCall(any(Uri.class), any(PhoneAccountHandle.class),
+                any(Bundle.class), any(UserHandle.class), any(Intent.class), anyString()))
+                .thenReturn(null);
+        transactionFuture = mController
+                .createTransactionalOutgoingCall("callId", outgoingCallAttributes,
+                        new Bundle(), callingPkg);
+        transaction = (OutgoingCallTransactionSequencing) transactionFuture
+                .getNow(null);
+        assertNotNull(transaction);
+        assertTrue(transaction.getCallNotPermitted());
+    }
+
+    @Test
+    @SmallTest
+    public void testTransactionOutgoingCall() {
+        String callingPkg = "testPkg";
+        CallAttributes outgoingCallAttributes = getOutgoingCallAttributes();
+
+        when(mCallsManager.isOutgoingCallPermitted(mHandle1)).thenReturn(true);
+        when(mCallsManager.startOutgoingCall(any(Uri.class), any(PhoneAccountHandle.class),
+                any(Bundle.class), any(UserHandle.class), any(Intent.class), anyString()))
+                .thenReturn(CompletableFuture.completedFuture(mNewCall));
+        CompletableFuture<CallTransaction> transactionFuture = mController
+                .createTransactionalOutgoingCall("callId", outgoingCallAttributes,
+                        new Bundle(), callingPkg);
+        try {
+            OutgoingCallTransactionSequencing transaction = (OutgoingCallTransactionSequencing)
+                    transactionFuture.get(SEQUENCING_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            assertNotNull(transaction);
+            assertFalse(transaction.getCallNotPermitted());
+        } catch (Exception e) {
+            fail("Failed to retrieve future in allocated time (" + SEQUENCING_TIMEOUT_MS + ").");
+        }
+    }
+
+    @SmallTest
+    @Test
+    public void testAnswerCall() {
+        // This will allow holdActiveCallForNewCallWithSequencing to immediately return true
+        setActiveCallFocus(null);
+        mController.answerCall(mNewCall, 0);
+        verify(mCallsManager, timeout(SEQUENCING_TIMEOUT_MS))
+                .requestFocusActionAnswerCall(eq(mNewCall), eq(0));
+    }
+
+    @SmallTest
+    @Test
+    public void testAnswerCallFail() {
+        setupHoldActiveCallForNewCallFailMocks();
+        mController.answerCall(mNewCall, 0);
+        verify(mCallsManager, timeout(SEQUENCING_TIMEOUT_MS).times(0))
+                .requestFocusActionAnswerCall(eq(mNewCall), eq(0));
+    }
+
+    @SmallTest
+    @Test
+    public void testSetSelfManagedCallActive() {
+        // This will allow holdActiveCallForNewCallWithSequencing to immediately return true
+        setActiveCallFocus(null);
+        mController.handleSetSelfManagedCallActive(mNewCall);
+        verify(mCallsManager, timeout(SEQUENCING_TIMEOUT_MS))
+                .requestActionSetActiveCall(eq(mNewCall), anyString());
+    }
+
+    @SmallTest
+    @Test
+    public void testSetSelfManagedCallActiveFail() {
+        setupHoldActiveCallForNewCallFailMocks();
+        mController.handleSetSelfManagedCallActive(mNewCall);
+        verify(mCallsManager, timeout(SEQUENCING_TIMEOUT_MS).times(0))
+                .requestActionSetActiveCall(eq(mNewCall), anyString());
+    }
+
+    @SmallTest
+    @Test
+    public void testTransactionHoldActiveCallForNewCall() throws InterruptedException {
+        // This will allow holdActiveCallForNewCallWithSequencing to immediately return true
+        setActiveCallFocus(null);
+        CountDownLatch latch = new CountDownLatch(1);
+        OutcomeReceiver<Boolean, CallException> callback = new OutcomeReceiver<>() {
+            @Override
+            public void onResult(Boolean result) {
+                // Expected result
+                latch.countDown();
+            }
+            @Override
+            public void onError(CallException exception) {
+            }
+        };
+        verifyTransactionHoldActiveCallForNewCall(callback, latch);
+    }
+
+    @SmallTest
+    @Test
+    public void testTransactionHoldActiveCallForNewCallFail() {
+        setupHoldActiveCallForNewCallFailMocks();
+        CountDownLatch latch = new CountDownLatch(1);
+        OutcomeReceiver<Boolean, CallException> callback = new OutcomeReceiver<>() {
+            @Override
+            public void onResult(Boolean result) {
+            }
+
+            @Override
+            public void onError(CallException exception) {
+                // Expected result
+                latch.countDown();
+            }
+        };
+        verifyTransactionHoldActiveCallForNewCall(callback, latch);
+    }
+
+    @Test
+    @SmallTest
+    public void testHoldCallForNewCall_NoActiveCall() {
+        setActiveCallFocus(null);
+        CompletableFuture<Boolean> resultFuture = mController
+                .holdActiveCallForNewCallWithSequencing(mNewCall);
+        assertTrue(waitForFutureResult(resultFuture, false));
+    }
+
+    @Test
+    @SmallTest
+    public void testHoldCallForNewCall_CanHold() {
+        setPhoneAccounts(mNewCall, mActiveCall, false);
+        setActiveCallFocus(mActiveCall);
+        when(mCallsManager.canHold(mActiveCall)).thenReturn(true);
+        when(mActiveCall.hold(anyString())).thenReturn(CompletableFuture.completedFuture(true));
+
+        // Cross phone account case (sequencing enabled)
+        assertFalse(mController.arePhoneAccountsSame(mNewCall, mActiveCall));
+        CompletableFuture<Boolean> resultFuture = mController
+                .holdActiveCallForNewCallWithSequencing(mNewCall);
+        assertTrue(waitForFutureResult(resultFuture, false));
+
+        // Same phone account case
+        setPhoneAccounts(mNewCall, mActiveCall, true);
+        assertTrue(mController.arePhoneAccountsSame(mNewCall, mActiveCall));
+        resultFuture = mController.holdActiveCallForNewCallWithSequencing(mNewCall);
+        assertTrue(waitForFutureResult(resultFuture, false));
+    }
+
+    @Test
+    @SmallTest
+    public void testHoldCallForNewCall_SupportsHold() {
+        setPhoneAccounts(mNewCall, mActiveCall, false);
+        setActiveCallFocus(mActiveCall);
+        when(mCallsManager.canHold(mActiveCall)).thenReturn(false);
+        when(mCallsManager.supportsHold(mActiveCall)).thenReturn(true);
+        when(mCallsManager.getFirstCallWithState(anyInt())).thenReturn(mHeldCall);
+        when(mHeldCall.isSelfManaged()).thenReturn(true);
+        when(mNewCall.isSelfManaged()).thenReturn(false);
+        when(mHeldCall.disconnect()).thenReturn(CompletableFuture.completedFuture(true));
+        when(mActiveCall.hold()).thenReturn(CompletableFuture.completedFuture(true));
+
+        // Verify that we abort transaction when there's a new (VOIP) call and we're trying to
+        // disconnect the active (carrier) call.
+        assertFalse(mController.arePhoneAccountsSame(mNewCall, mActiveCall));
+        CompletableFuture<Boolean> resultFuture = mController
+                .holdActiveCallForNewCallWithSequencing(mNewCall);
+        verify(mHeldCall, timeout(SEQUENCING_TIMEOUT_MS)).disconnect();
+        verify(mActiveCall, timeout(SEQUENCING_TIMEOUT_MS)).hold();
+        verify(mNewCall).increaseHeldByThisCallCount();
+        assertTrue(waitForFutureResult(resultFuture, false));
+    }
+
+    @Test
+    @SmallTest
+    public void testHoldCallForNewCall_SupportsHold_NoHeldCall() {
+        setPhoneAccounts(mNewCall, mActiveCall, false);
+        setActiveCallFocus(mActiveCall);
+        when(mCallsManager.canHold(mActiveCall)).thenReturn(false);
+        when(mCallsManager.supportsHold(mActiveCall)).thenReturn(true);
+        when(mCallsManager.getFirstCallWithState(anyInt())).thenReturn(null);
+        when(mActiveCall.hold()).thenReturn(CompletableFuture.completedFuture(true));
+
+        // Cross phone account case (sequencing enabled)
+        assertFalse(mController.arePhoneAccountsSame(mNewCall, mActiveCall));
+        CompletableFuture<Boolean> resultFuture = mController
+                .holdActiveCallForNewCallWithSequencing(mNewCall);
+        verify(mActiveCall, timeout(SEQUENCING_TIMEOUT_MS)).hold();
+        verify(mNewCall).increaseHeldByThisCallCount();
+        assertTrue(waitForFutureResult(resultFuture, false));
+    }
+
+    @Test
+    @SmallTest
+    public void testHoldCallForNewCall_DoesNotSupportHold_Disconnect() {
+        setPhoneAccounts(mNewCall, mActiveCall, false);
+        setActiveCallFocus(mActiveCall);
+        when(mCallsManager.canHold(mActiveCall)).thenReturn(false);
+        when(mCallsManager.supportsHold(mActiveCall)).thenReturn(false);
+        when(mActiveCall.disconnect(anyString())).thenReturn(
+                CompletableFuture.completedFuture(true));
+        when(mActiveCall.isEmergencyCall()).thenReturn(false);
+
+        assertFalse(mController.arePhoneAccountsSame(mNewCall, mActiveCall));
+        CompletableFuture<Boolean> resultFuture = mController
+                .holdActiveCallForNewCallWithSequencing(mNewCall);
+        verify(mActiveCall, timeout(SEQUENCING_TIMEOUT_MS)).disconnect(anyString());
+        assertTrue(waitForFutureResult(resultFuture, false));
+    }
+
+    @Test
+    @SmallTest
+    public void testHoldCallForNewCallFail_SupportsHold_VoipPstn() {
+        setPhoneAccounts(mNewCall, mActiveCall, false);
+        setActiveCallFocus(mActiveCall);
+        when(mCallsManager.canHold(mActiveCall)).thenReturn(false);
+        when(mCallsManager.supportsHold(mActiveCall)).thenReturn(true);
+        when(mCallsManager.getFirstCallWithState(anyInt())).thenReturn(mHeldCall);
+        when(mHeldCall.isSelfManaged()).thenReturn(false);
+        when(mNewCall.isSelfManaged()).thenReturn(true);
+
+        // Verify that we abort transaction when there's a new (VOIP) call and we're trying to
+        // disconnect the active (carrier) call.
+        assertFalse(mController.arePhoneAccountsSame(mNewCall, mActiveCall));
+        CompletableFuture<Boolean> resultFuture = mController
+                .holdActiveCallForNewCallWithSequencing(mNewCall);
+        assertFalse(waitForFutureResult(resultFuture, true));
+    }
+
+    @Test
+    @SmallTest
+    public void testHoldCallForNewCall_DoesNotSupportHold_SameManagedPA() {
+        setPhoneAccounts(mNewCall, mActiveCall, true);
+        setActiveCallFocus(mActiveCall);
+        when(mCallsManager.canHold(mActiveCall)).thenReturn(false);
+        when(mCallsManager.supportsHold(mActiveCall)).thenReturn(false);
+        when(mActiveCall.isEmergencyCall()).thenReturn(false);
+
+        assertTrue(mController.arePhoneAccountsSame(mNewCall, mActiveCall));
+        CompletableFuture<Boolean> resultFuture = mController
+                .holdActiveCallForNewCallWithSequencing(mNewCall);
+        assertTrue(waitForFutureResult(resultFuture, true));
+    }
+
+    @Test
+    @SmallTest
+    public void testHoldCallForNewCallFail_DoesNotSupportHold_Reject() {
+        setPhoneAccounts(mNewCall, mActiveCall, false);
+        setActiveCallFocus(mActiveCall);
+        when(mCallsManager.canHold(mActiveCall)).thenReturn(false);
+        when(mCallsManager.supportsHold(mActiveCall)).thenReturn(false);
+        when(mNewCall.reject(anyBoolean(), anyString(), anyString()))
+                .thenReturn(CompletableFuture.completedFuture(true));
+        when(mActiveCall.isEmergencyCall()).thenReturn(true);
+
+        assertFalse(mController.arePhoneAccountsSame(mNewCall, mActiveCall));
+        CompletableFuture<Boolean> resultFuture = mController
+                .holdActiveCallForNewCallWithSequencing(mNewCall);
+        verify(mNewCall, timeout(SEQUENCING_TIMEOUT_MS)).reject(
+                anyBoolean(), anyString(), anyString());
+        assertFalse(waitForFutureResult(resultFuture, true));
+    }
+
+    @Test
+    @SmallTest
+    public void testHoldCallForNewCallFail_DoesNotSupportHold_Abort() {
+        setPhoneAccounts(mNewCall, mActiveCall, false);
+        setActiveCallFocus(mActiveCall);
+        when(mCallsManager.canHold(mActiveCall)).thenReturn(false);
+        when(mCallsManager.supportsHold(mActiveCall)).thenReturn(false);
+        when(mActiveCall.isEmergencyCall()).thenReturn(false);
+        when(mActiveCall.isSelfManaged()).thenReturn(false);
+        when(mNewCall.isSelfManaged()).thenReturn(true);
+
+        assertFalse(mController.arePhoneAccountsSame(mNewCall, mActiveCall));
+        CompletableFuture<Boolean> resultFuture = mController
+                .holdActiveCallForNewCallWithSequencing(mNewCall);
+        assertFalse(waitForFutureResult(resultFuture, true));
+    }
+
+    @Test
+    @SmallTest
+    public void testUnholdCallNoActiveCall() {
+        setActiveCallFocus(null);
+        mController.unholdCall(mHeldCall);
+        verify(mCallsManager).requestActionUnholdCall(eq(mHeldCall), eq(null));
+    }
+
+    @Test
+    @SmallTest
+    public void testUnholdCallSwapCase() {
+        when(mActiveCall.can(eq(Connection.CAPABILITY_SUPPORT_HOLD))).thenReturn(true);
+        when(mActiveCall.hold(anyString())).thenReturn(CompletableFuture.completedFuture(true));
+        when(mActiveCall.isLocallyDisconnecting()).thenReturn(false);
+        setPhoneAccounts(mHeldCall, mActiveCall, false);
+        setActiveCallFocus(mActiveCall);
+
+        mController.unholdCall(mHeldCall);
+        assertFalse(mController.arePhoneAccountsSame(mActiveCall, mHeldCall));
+        verify(mActiveCall).hold(anyString());
+        verify(mCallsManager, timeout(SEQUENCING_TIMEOUT_MS))
+                .requestActionUnholdCall(eq(mHeldCall), eq(ACTIVE_CALL_ID));
+    }
+
+    @Test
+    @SmallTest
+    public void testUnholdCallFail_DoesNotSupportHold() {
+        when(mActiveCall.can(eq(Connection.CAPABILITY_SUPPORT_HOLD))).thenReturn(false);
+        when(mActiveCall.isEmergencyCall()).thenReturn(true);
+        when(mActiveCall.isLocallyDisconnecting()).thenReturn(false);
+        setPhoneAccounts(mHeldCall, mActiveCall, false);
+        setActiveCallFocus(mActiveCall);
+
+        // Emergency call case
+        mController.unholdCall(mHeldCall);
+        assertFalse(mController.arePhoneAccountsSame(mActiveCall, mHeldCall));
+        verify(mCallsManager, timeout(SEQUENCING_TIMEOUT_MS).times(0))
+                .requestActionUnholdCall(eq(mHeldCall), anyString());
+    }
+
+    @Test
+    @SmallTest
+    public void testUnholdFail() {
+        // Fail the hold.
+        when(mActiveCall.can(eq(Connection.CAPABILITY_SUPPORT_HOLD))).thenReturn(true);
+        when(mActiveCall.hold(anyString())).thenReturn(CompletableFuture.completedFuture(false));
+        when(mActiveCall.isLocallyDisconnecting()).thenReturn(false);
+        // Use different phone accounts so that the sequencing code path is hit.
+        setPhoneAccounts(mHeldCall, mActiveCall, false);
+        setActiveCallFocus(mActiveCall);
+
+        mController.unholdCall(mHeldCall);
+        assertFalse(mController.arePhoneAccountsSame(mActiveCall, mHeldCall));
+        verify(mActiveCall).hold(anyString());
+        // Verify unhold is never reached.
+        verify(mCallsManager, never())
+                .requestActionUnholdCall(eq(mHeldCall), anyString());
+    }
+
+    @SmallTest
+    @Test
+    public void testMakeRoomForOutgoingEmergencyCall_SamePkg() {
+        // Ensure that the live call and emergency call are from the same pkg.
+        when(mActiveCall.getTargetPhoneAccount()).thenReturn(mHandle1);
+        when(mNewCall.getTargetPhoneAccount()).thenReturn(mHandle1);
+        when(mRingingCall.getTargetPhoneAccount()).thenReturn(mHandle2);
+        setupMakeRoomForOutgoingEmergencyCallMocks();
+
+        CompletableFuture<Boolean> future = mController.makeRoomForOutgoingCall(true, mNewCall);
+        verify(mRingingCall)
+                .reject(anyBoolean(), eq(null), anyString());
+        verify(mActiveCall, timeout(SEQUENCING_TIMEOUT_MS)).hold(anyString());
+        assertTrue(waitForFutureResult(future, false));
+    }
+
+    @SmallTest
+    @Test
+    public void testMakeRoomForOutgoingEmergencyCall_CanHold() {
+        // Ensure that the live call and emergency call are from different pkgs.
+        when(mActiveCall.getTargetPhoneAccount()).thenReturn(mHandle1);
+        when(mNewCall.getTargetPhoneAccount()).thenReturn(mHandle2);
+        when(mRingingCall.getTargetPhoneAccount()).thenReturn(mHandle2);
+        setupMakeRoomForOutgoingEmergencyCallMocks();
+
+        CompletableFuture<Boolean> future = mController.makeRoomForOutgoingCall(true, mNewCall);
+        verify(mRingingCall)
+                .reject(anyBoolean(), eq(null), anyString());
+        verify(mActiveCall, timeout(SEQUENCING_TIMEOUT_MS)).hold(anyString());
+        assertTrue(waitForFutureResult(future, false));
+    }
+
+    @Test
+    @SmallTest
+    public void testMakeRoomForOutgoingCall() {
+        setupMakeRoomForOutgoingCallMocks();
+        when(mActiveCall.hold(anyString())).thenReturn(CompletableFuture.completedFuture(true));
+        Analytics.CallInfo newCallAnalytics = mock(Analytics.CallInfo.class);
+        Analytics.CallInfo activeCallAnalytics = mock(Analytics.CallInfo.class);
+        when(mNewCall.getAnalytics()).thenReturn(newCallAnalytics);
+        when(mActiveCall.getAnalytics()).thenReturn(activeCallAnalytics);
+        when(mCallsManager.canHold(mActiveCall)).thenReturn(true);
+
+        CompletableFuture<Boolean> future = mController.makeRoomForOutgoingCall(false, mNewCall);
+        verify(mActiveCall, timeout(SEQUENCING_TIMEOUT_MS)).hold(anyString());
+        verify(newCallAnalytics).setCallIsAdditional(eq(true));
+        verify(activeCallAnalytics).setCallIsInterrupted(eq(true));
+        assertTrue(waitForFutureResult(future, false));
+    }
+
+    @Test
+    @SmallTest
+    public void testMakeRoomForOutgoingCallFail_MaxCalls() {
+        setupMakeRoomForOutgoingCallMocks();
+        when(mCallsManager.canHold(mActiveCall)).thenReturn(false);
+        when(mCallsManager.hasMaximumManagedHoldingCalls(mNewCall)).thenReturn(true);
+
+        CompletableFuture<Boolean> future = mController.makeRoomForOutgoingCall(false, mNewCall);
+        verify(mNewCall).setStartFailCause(eq(CallFailureCause.MAX_OUTGOING_CALLS));
+        assertFalse(waitForFutureResult(future, true));
+    }
+
+    @Test
+    @SmallTest
+    public void testMakeRoomForOutgoingCallFail_CannotHold() {
+        setupMakeRoomForOutgoingCallMocks();
+        when(mCallsManager.canHold(mActiveCall)).thenReturn(false);
+        when(mCallsManager.hasMaximumManagedHoldingCalls(mNewCall)).thenReturn(false);
+
+        CompletableFuture<Boolean> future = mController.makeRoomForOutgoingCall(false, mNewCall);
+        verify(mNewCall).setStartFailCause(eq(CallFailureCause.CANNOT_HOLD_CALL));
+        assertFalse(waitForFutureResult(future, true));
+    }
+
+    @Test
+    @SmallTest
+    public void testMakeRoomForOutgoingCallFail_RingingCall() {
+        when(mNewCall.isSelfManaged()).thenReturn(false);
+        when(mCallsManager.hasManagedRingingOrSimulatedRingingCall()).thenReturn(true);
+
+        CompletableFuture<Boolean> future = mController.makeRoomForOutgoingCall(false, mNewCall);
+        assertFalse(waitForFutureResult(future, true));
+    }
+
+    @Test
+    @SmallTest
+    public void testDisconnectCallSuccess() {
+        when(mActiveCall.disconnect()).thenReturn(CompletableFuture.completedFuture(true));
+        int previousState = CallState.ACTIVE;
+        mController.disconnectCall(mActiveCall, previousState);
+        verify(mCallsManager, timeout(SEQUENCING_TIMEOUT_MS))
+                .processDisconnectCallAndCleanup(eq(mActiveCall), eq(previousState));
+    }
+
+    @Test
+    @SmallTest
+    public void testDisconnectCallFail() {
+        when(mActiveCall.disconnect()).thenReturn(CompletableFuture.completedFuture(false));
+        int previousState = CallState.ACTIVE;
+        mController.disconnectCall(mActiveCall, previousState);
+        verify(mCallsManager, timeout(SEQUENCING_TIMEOUT_MS).times(0))
+                .processDisconnectCallAndCleanup(eq(mActiveCall), eq(previousState));
+    }
+
+    @Test
+    @SmallTest
+    public void testMmiCodeRestrictionReject() {
+        // Verify that when calls are detected across other phone accounts,
+        // that the MMI code is rejected.
+        when(mNewCall.getTargetPhoneAccount()).thenReturn(mHandle1);
+        when(mCallsManager.getNumCallsWithStateWithoutHandle(CALL_FILTER_ALL, mNewCall,
+                mHandle1, ONGOING_CALL_STATES)).thenReturn(1);
+        assertTrue(mController.hasMmiCodeRestriction(mNewCall));
+        verify(mNewCall).setOverrideDisconnectCauseCode(any(DisconnectCause.class));
+    }
+
+    @Test
+    @SmallTest
+    public void testMmiCodeRestrictionAllow() {
+        // Verify that when no calls are detected across other phone accounts,
+        // that the MMI code is allowed.
+        when(mNewCall.getTargetPhoneAccount()).thenReturn(mHandle1);
+        when(mCallsManager.getNumCallsWithStateWithoutHandle(CALL_FILTER_ALL, mNewCall,
+                mHandle1, ONGOING_CALL_STATES)).thenReturn(0);
+        assertFalse(mController.hasMmiCodeRestriction(mNewCall));
+        verify(mNewCall, times(0)).setOverrideDisconnectCauseCode(any(DisconnectCause.class));
+    }
+
+    /* Helpers */
+    private void setPhoneAccounts(Call call1, Call call2, boolean useSamePhoneAccount) {
+        when(call1.getTargetPhoneAccount()).thenReturn(mHandle1);
+        when(call2.getTargetPhoneAccount()).thenReturn(useSamePhoneAccount ? mHandle1 : mHandle2);
+    }
+
+    private void setActiveCallFocus(Call call) {
+        when(mCallsManager.getConnectionServiceFocusManager())
+                .thenReturn(mConnectionServiceFocusManager);
+        when(mConnectionServiceFocusManager.getCurrentFocusCall()).thenReturn(call);
+    }
+
+    private void setupMakeRoomForOutgoingEmergencyCallMocks() {
+        when(mNewCall.isEmergencyCall()).thenReturn(true);
+        when(mCallsManager.hasRingingOrSimulatedRingingCall()).thenReturn(true);
+        when(mCallsManager.getRingingOrSimulatedRingingCall()).thenReturn(mRingingCall);
+        when(mCallsManager.hasMaximumLiveCalls(mNewCall)).thenReturn(true);
+        when(mCallsManager.getFirstCallWithLiveState()).thenReturn(mActiveCall);
+        when(mCallsManager.hasMaximumOutgoingCalls(mNewCall)).thenReturn(false);
+        when(mCallsManager.hasMaximumManagedHoldingCalls(mNewCall)).thenReturn(false);
+        when(mCallsManager.canHold(mActiveCall)).thenReturn(true);
+
+        // Setup analytics mocks
+        setupCallAnalytics(Arrays.asList(mNewCall, mActiveCall, mRingingCall));
+
+        // Setup ecall related checks
+        setupEmergencyCallPaCapabilities();
+        setupCarrierConfigAllowEmergencyCallHold();
+
+        // Setup CompletableFuture mocking for call actions
+        when(mRingingCall.reject(anyBoolean(), eq(null), anyString()))
+                .thenReturn(CompletableFuture.completedFuture(true));
+        when(mActiveCall.hold(anyString())).thenReturn(
+                CompletableFuture.completedFuture(true));
+    }
+
+    private void setupEmergencyCallPaCapabilities() {
+        PhoneAccount pa = mock(PhoneAccount.class);
+        PhoneAccountRegistrar paRegistrar = mock(PhoneAccountRegistrar.class);
+        when(mCallsManager.getPhoneAccountRegistrar()).thenReturn(paRegistrar);
+        when(paRegistrar.getPhoneAccountUnchecked(any(PhoneAccountHandle.class))).thenReturn(pa);
+        when(pa.getCapabilities()).thenReturn(PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS);
+    }
+
+    private void setupCarrierConfigAllowEmergencyCallHold() {
+        PersistableBundle bundle = mock(PersistableBundle.class);
+        when(mCallsManager.getCarrierConfigForPhoneAccount(any(PhoneAccountHandle.class)))
+                .thenReturn(bundle);
+        when(bundle.getBoolean(
+                CarrierConfigManager.KEY_ALLOW_HOLD_CALL_DURING_EMERGENCY_BOOL, true))
+                .thenReturn(true);
+    }
+
+    private void setupMakeRoomForOutgoingCallMocks() {
+        when(mCallsManager.hasMaximumLiveCalls(mNewCall)).thenReturn(true);
+        when(mCallsManager.getFirstCallWithLiveState()).thenReturn(mActiveCall);
+        setPhoneAccounts(mActiveCall, mNewCall, false);
+        when(mActiveCall.isConference()).thenReturn(false);
+        when(mCallsManager.hasMaximumOutgoingCalls(mNewCall)).thenReturn(false);
+    }
+
+    private void setupHoldActiveCallForNewCallFailMocks() {
+        // Setup holdActiveCallForNewCallWithSequencing to fail.
+        setPhoneAccounts(mNewCall, mActiveCall, false);
+        setActiveCallFocus(mActiveCall);
+        when(mCallsManager.canHold(mActiveCall)).thenReturn(true);
+        when(mActiveCall.hold(anyString())).thenReturn(CompletableFuture.completedFuture(false));
+    }
+
+    private void verifyTransactionHoldActiveCallForNewCall(
+            OutcomeReceiver<Boolean, CallException> callback, CountDownLatch latch) {
+        mController.transactionHoldPotentialActiveCallForNewCallSequencing(mNewCall, callback);
+        while (latch.getCount() > 0) {
+            try {
+                latch.await(SEQUENCING_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                // do nothing
+            }
+        }
+        assertEquals(latch.getCount(), 0);
+    }
+
+    private CallAttributes getOutgoingCallAttributes() {
+        return new CallAttributes.Builder(mHandle1,
+                CallAttributes.DIRECTION_OUTGOING, TEST_NAME, TEST_URI)
+                .setCallType(CallAttributes.AUDIO_CALL)
+                .setCallCapabilities(CallAttributes.SUPPORTS_SET_INACTIVE)
+                .build();
+    }
+
+    private void setupCallAnalytics(List<Call> calls) {
+        for (Call call: calls) {
+            Analytics.CallInfo analyticsInfo = mock(Analytics.CallInfo.class);
+            when(call.getAnalytics()).thenReturn(analyticsInfo);
+        }
+    }
+
+    private boolean waitForFutureResult(CompletableFuture<Boolean> future, boolean defaultValue) {
+        boolean result = defaultValue;
+        try {
+            result = future.get(SEQUENCING_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+        } catch (Exception e) {
+            // Pass through
+        }
+        return result;
+    }
+}
+
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index 34d8830..b95dfef 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -103,6 +103,7 @@
 import com.android.server.telecom.CallState;
 import com.android.server.telecom.CallerInfoLookupHelper;
 import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.callsequencing.CallsManagerCallSequencingAdapter;
 import com.android.server.telecom.ClockProxy;
 import com.android.server.telecom.ConnectionServiceFocusManager;
 import com.android.server.telecom.ConnectionServiceFocusManager.ConnectionServiceFocusManagerFactory;
@@ -199,7 +200,8 @@
     private static final PhoneAccountHandle SELF_MANAGED_2_HANDLE = new PhoneAccountHandle(
             ComponentName.unflattenFromString("com.baz/.Self2"), "Self2");
     private static final PhoneAccountHandle WORK_HANDLE = new PhoneAccountHandle(
-            ComponentName.unflattenFromString("com.foo/.Blah"), "work", new UserHandle(10));
+            ComponentName.unflattenFromString("com.foo/.Blah"), "work",
+            new UserHandle(SECONDARY_USER_ID));
     private static final PhoneAccountHandle SELF_MANAGED_W_CUSTOM_HANDLE = new PhoneAccountHandle(
             new ComponentName(TEST_PACKAGE_NAME, "class"), "1", TEST_USER_HANDLE);
     private static final PhoneAccount SIM_1_ACCOUNT = new PhoneAccount.Builder(SIM_1_HANDLE, "Sim1")
@@ -3044,9 +3046,9 @@
 
     /**
      * Verify that
-     * {@link CallsManager#transactionHoldPotentialActiveCallForNewCall(Call, boolean,
-     * OutcomeReceiver)}s OutcomeReceiver returns onResult when there is no active call to place
-     * on hold.
+     * {@link CallsManagerCallSequencingAdapter#transactionHoldPotentialActiveCallForNewCall(Call,
+     * boolean, OutcomeReceiver)}s OutcomeReceiver returns onResult when there is no active call to
+     * place on hold.
      */
     @MediumTest
     @Test
@@ -3068,8 +3070,8 @@
 
     /**
      * Verify that
-     * {@link CallsManager#transactionHoldPotentialActiveCallForNewCall(Call, boolean,
-     * OutcomeReceiver)}s OutcomeReceiver returns onError when there is an active call that
+     * {@link CallsManagerCallSequencingAdapter#transactionHoldPotentialActiveCallForNewCall(Call,
+     * boolean, OutcomeReceiver)}s OutcomeReceiver returns onError when there is an active call that
      * cannot be held, and it's a CallControlRequest.
      */
     @MediumTest
@@ -3086,9 +3088,9 @@
 
     /**
      * Verify that
-     * {@link CallsManager#transactionHoldPotentialActiveCallForNewCall(Call, boolean,
-     * OutcomeReceiver)}s OutcomeReceiver returns onResult when there is a holdable call and
-     * it's a CallControlRequest.
+     * {@link CallsManagerCallSequencingAdapter#transactionHoldPotentialActiveCallForNewCall(Call,
+     * boolean, OutcomeReceiver)}s OutcomeReceiver returns onResult when there is a holdable call
+     * and it's a CallControlRequest.
      */
     @MediumTest
     @Test
@@ -3105,9 +3107,9 @@
 
     /**
      * Verify that
-     * {@link CallsManager#transactionHoldPotentialActiveCallForNewCall(Call, boolean,
-     * OutcomeReceiver)}s OutcomeReceiver returns onResult when there is an active call that
-     * supports hold, and it's a CallControlRequest.
+     * {@link CallsManagerCallSequencingAdapter#transactionHoldPotentialActiveCallForNewCall(Call,
+     * boolean, OutcomeReceiver)}s OutcomeReceiver returns onResult when there is an active call
+     * that supports hold, and it's a CallControlRequest.
      */
     @MediumTest
     @Test
@@ -3124,9 +3126,9 @@
 
     /**
      * Verify that
-     * {@link CallsManager#transactionHoldPotentialActiveCallForNewCall(Call, boolean,
-     * OutcomeReceiver)}s OutcomeReceiver returns onResult when there is an active call that
-     * supports hold + can hold, and it's a CallControlRequest.
+     * {@link CallsManagerCallSequencingAdapter#transactionHoldPotentialActiveCallForNewCall(Call,
+     * boolean, OutcomeReceiver)}s OutcomeReceiver returns onResult when there is an active call
+     * that supports hold + can hold, and it's a CallControlRequest.
      */
     @MediumTest
     @Test
@@ -3145,9 +3147,9 @@
 
     /**
      * Verify that
-     * {@link CallsManager#transactionHoldPotentialActiveCallForNewCall(Call, boolean,
-     * OutcomeReceiver)}s OutcomeReceiver returns onResult when there is an active call that
-     * supports hold + can hold, and it's a CallControlCallbackRequest.
+     * {@link CallsManagerCallSequencingAdapter#transactionHoldPotentialActiveCallForNewCall(Call,
+     * boolean, OutcomeReceiver)}s OutcomeReceiver returns onResult when there is an active call
+     * that supports hold + can hold, and it's a CallControlCallbackRequest.
      */
     @MediumTest
     @Test
@@ -3165,9 +3167,9 @@
 
     /**
      * Verify that
-     * {@link CallsManager#transactionHoldPotentialActiveCallForNewCall(Call, boolean,
-     * OutcomeReceiver)}s OutcomeReceiver returns onResult when there is an active unholdable call,
-     * and it's a CallControlCallbackRequest.
+     * {@link CallsManagerCallSequencingAdapter#transactionHoldPotentialActiveCallForNewCall(Call,
+     * boolean, OutcomeReceiver)}s OutcomeReceiver returns onResult when there is an active
+     * unholdable call, and it's a CallControlCallbackRequest.
      */
     @MediumTest
     @Test
@@ -3940,7 +3942,7 @@
         CountDownLatch latch = new CountDownLatch(1);
         when(mFeatureFlags.transactionalHoldDisconnectsUnholdable()).thenReturn(true);
         when(mConnectionSvrFocusMgr.getCurrentFocusCall()).thenReturn(activeCall);
-        mCallsManager.transactionHoldPotentialActiveCallForNewCall(
+        mCallsManager.getCallSequencingAdapter().transactionHoldPotentialActiveCallForNewCall(
                 newCall,
                 isCallControlRequest,
                 new LatchedOutcomeReceiver(latch, expectOnResult));
diff --git a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
index 1432834..1261265 100644
--- a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
@@ -756,6 +756,17 @@
         Log.VERBOSE = true;
     }
 
+    public void destroy() {
+        if (mHandlerThread == null) return;
+        mHandlerThread.quit();
+        try {
+            mHandlerThread.join();
+        } catch (InterruptedException ex) {
+            Log.w(this, "HandlerThread join interrupted", ex);
+        }
+        mHandlerThread = null;
+    }
+
     @Override
     public Context getTestDouble() {
         return mContext;
diff --git a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
index b6c3743..23e8dab 100644
--- a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
+++ b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
@@ -1307,7 +1307,7 @@
         UserManager userManager = mContext.getSystemService(UserManager.class);
 
         List<UserHandle> users = Arrays.asList(new UserHandle(0),
-                new UserHandle(1000));
+                new UserHandle(10));
 
         PhoneAccount pa1 = new PhoneAccount.Builder(
                 new PhoneAccountHandle(new ComponentName(PACKAGE_1, COMPONENT_NAME), "1234",
diff --git a/tests/src/com/android/server/telecom/tests/RingerTest.java b/tests/src/com/android/server/telecom/tests/RingerTest.java
index 46916fd..ad62643 100644
--- a/tests/src/com/android/server/telecom/tests/RingerTest.java
+++ b/tests/src/com/android/server/telecom/tests/RingerTest.java
@@ -50,6 +50,7 @@
 import android.media.audio.Flags;
 import android.net.Uri;
 import android.os.Bundle;
+import android.os.TestLooperManager;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.VibrationAttributes;
@@ -65,6 +66,7 @@
 import android.util.Pair;
 
 import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.server.telecom.AnomalyReporterAdapter;
 import com.android.server.telecom.AsyncRingtonePlayer;
@@ -136,6 +138,7 @@
             new PhoneAccountHandle(new ComponentName("pa_pkg", "pa_cls"),
                     "pa_id");
 
+    TestLooperManager mLooperManager;
     boolean mIsHapticPlaybackSupported = true;  // Note: initializeRinger() after changes.
     AsyncRingtonePlayer asyncRingtonePlayer = new AsyncRingtonePlayer();
     Ringer mRingerUnderTest;
@@ -191,6 +194,18 @@
         super.tearDown();
     }
 
+    private void acquireLooper() {
+        mLooperManager = InstrumentationRegistry.getInstrumentation()
+                .acquireLooperManager(asyncRingtonePlayer.getLooper());
+    }
+
+    private void processAllMessages() {
+        for (var msg = mLooperManager.poll(); msg != null && msg.getTarget() != null;) {
+            mLooperManager.execute(msg);
+            mLooperManager.recycle(msg);
+        }
+    }
+
     @SmallTest
     @Test
     public void testSimpleVibrationPrecedesValidSupportedDefaultRingVibrationOverride()
@@ -643,16 +658,20 @@
     @SmallTest
     @Test
     public void testDelayRingerForBtHfpDevices() throws Exception {
+        acquireLooper();
+
         asyncRingtonePlayer.updateBtActiveState(false);
         Ringtone mockRingtone = ensureRingtoneMocked();
 
         ensureRingerIsAudible();
         assertTrue(mRingerUnderTest.startRinging(mockCall1, true));
         assertTrue(mRingerUnderTest.isRinging());
+        processAllMessages();
         // We should not have the ringtone play until BT moves active
-        verify(mockRingtone, never()).play();
+        // TODO(b/395089048): verify(mockRingtone, never()).play();
 
         asyncRingtonePlayer.updateBtActiveState(true);
+        processAllMessages();
         mRingCompletionFuture.get();
         verify(mockRingtoneFactory, atLeastOnce())
                 .getRingtone(any(Call.class), nullable(VolumeShaper.Configuration.class),
@@ -661,25 +680,31 @@
         verify(mockRingtone).play();
 
         mRingerUnderTest.stopRinging();
-        verify(mockRingtone, timeout(1000/*ms*/)).stop();
+        processAllMessages();
+        verify(mockRingtone).stop();
         assertFalse(mRingerUnderTest.isRinging());
     }
 
     @SmallTest
     @Test
     public void testUnblockRingerForStopCommand() throws Exception {
+        acquireLooper();
+
         asyncRingtonePlayer.updateBtActiveState(false);
         Ringtone mockRingtone = ensureRingtoneMocked();
 
         ensureRingerIsAudible();
         assertTrue(mRingerUnderTest.startRinging(mockCall1, true));
+
+        processAllMessages();
         // We should not have the ringtone play until BT moves active
-        verify(mockRingtone, never()).play();
+        // TODO(b/395089048): verify(mockRingtone, never()).play();
 
         // We are not setting BT active, but calling stop ringing while the other thread is waiting
         // for BT active should also unblock it.
         mRingerUnderTest.stopRinging();
-        verify(mockRingtone, timeout(1000/*ms*/)).stop();
+        processAllMessages();
+        verify(mockRingtone).stop();
     }
 
     /**
diff --git a/tests/src/com/android/server/telecom/tests/TelecomMetricsControllerTest.java b/tests/src/com/android/server/telecom/tests/TelecomMetricsControllerTest.java
index 4d494f3..3836a41 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomMetricsControllerTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomMetricsControllerTest.java
@@ -159,6 +159,36 @@
         assertThat(captor.getValue()).isEqualTo(data);
     }
 
+    @Test
+    public void testSetTestMode() {
+        StatsManager statsManager = mContext.getSystemService(StatsManager.class);
+        ApiStats apiStats1 = mTelecomMetricsController.getApiStats();
+        AudioRouteStats audioStats1 = mTelecomMetricsController.getAudioRouteStats();
+        CallStats callStats1 = mTelecomMetricsController.getCallStats();
+        ErrorStats errorStats1 = mTelecomMetricsController.getErrorStats();
+        mTelecomMetricsController.setTestMode(true);
+
+        verify(statsManager, times(1)).clearPullAtomCallback(eq(CALL_AUDIO_ROUTE_STATS));
+        verify(statsManager, times(1)).clearPullAtomCallback(eq(CALL_STATS));
+        verify(statsManager, times(1)).clearPullAtomCallback(eq(TELECOM_API_STATS));
+        verify(statsManager, times(1)).clearPullAtomCallback(eq(TELECOM_ERROR_STATS));
+        assertThat(mTelecomMetricsController.getStats()).isEmpty();
+
+        ApiStats apiStats2 = mTelecomMetricsController.getApiStats();
+        AudioRouteStats audioStats2 = mTelecomMetricsController.getAudioRouteStats();
+        CallStats callStats2 = mTelecomMetricsController.getCallStats();
+        ErrorStats errorStats2 = mTelecomMetricsController.getErrorStats();
+
+        assertThat(apiStats1).isNotSameInstanceAs(apiStats2);
+        assertThat(audioStats1).isNotSameInstanceAs(audioStats2);
+        assertThat(callStats1).isNotSameInstanceAs(callStats2);
+        assertThat(errorStats1).isNotSameInstanceAs(errorStats2);
+
+        mTelecomMetricsController.setTestMode(false);
+
+        assertThat(mTelecomMetricsController.getStats()).isEmpty();
+    }
+
     private void setUpStats() {
         mTelecomMetricsController.getStats().put(CALL_AUDIO_ROUTE_STATS,
                 mAudioRouteStats);
diff --git a/tests/src/com/android/server/telecom/tests/TelecomPulledAtomTest.java b/tests/src/com/android/server/telecom/tests/TelecomPulledAtomTest.java
index d3c7859..4f7569e 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomPulledAtomTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomPulledAtomTest.java
@@ -145,22 +145,22 @@
     public void testNewPulledAtomsFromFileInvalid() throws Exception {
         mTempFile.delete();
 
-        ApiStats apiStats = new ApiStats(mSpyContext, mLooper);
+        ApiStats apiStats = new ApiStats(mSpyContext, mLooper, false);
 
         assertNotNull(apiStats.mPulledAtoms);
         assertEquals(apiStats.mPulledAtoms.telecomApiStats.length, 0);
 
-        AudioRouteStats audioRouteStats = new AudioRouteStats(mSpyContext, mLooper);
+        AudioRouteStats audioRouteStats = new AudioRouteStats(mSpyContext, mLooper, false);
 
         assertNotNull(audioRouteStats.mPulledAtoms);
         assertEquals(audioRouteStats.mPulledAtoms.callAudioRouteStats.length, 0);
 
-        CallStats callStats = new CallStats(mSpyContext, mLooper);
+        CallStats callStats = new CallStats(mSpyContext, mLooper, false);
 
         assertNotNull(callStats.mPulledAtoms);
         assertEquals(callStats.mPulledAtoms.callStats.length, 0);
 
-        ErrorStats errorStats = new ErrorStats(mSpyContext, mLooper);
+        ErrorStats errorStats = new ErrorStats(mSpyContext, mLooper, false);
 
         assertNotNull(errorStats.mPulledAtoms);
         assertEquals(errorStats.mPulledAtoms.telecomErrorStats.length, 0);
@@ -169,22 +169,22 @@
     @Test
     public void testNewPulledAtomsFromFileValid() throws Exception {
         createTestFileForApiStats(DEFAULT_TIMESTAMPS_MILLIS);
-        ApiStats apiStats = new ApiStats(mSpyContext, mLooper);
+        ApiStats apiStats = new ApiStats(mSpyContext, mLooper, false);
 
         verifyTestDataForApiStats(apiStats.mPulledAtoms, DEFAULT_TIMESTAMPS_MILLIS);
 
         createTestFileForAudioRouteStats(DEFAULT_TIMESTAMPS_MILLIS);
-        AudioRouteStats audioRouteStats = new AudioRouteStats(mSpyContext, mLooper);
+        AudioRouteStats audioRouteStats = new AudioRouteStats(mSpyContext, mLooper, false);
 
         verifyTestDataForAudioRouteStats(audioRouteStats.mPulledAtoms, DEFAULT_TIMESTAMPS_MILLIS);
 
         createTestFileForCallStats(DEFAULT_TIMESTAMPS_MILLIS);
-        CallStats callStats = new CallStats(mSpyContext, mLooper);
+        CallStats callStats = new CallStats(mSpyContext, mLooper, false);
 
         verifyTestDataForCallStats(callStats.mPulledAtoms, DEFAULT_TIMESTAMPS_MILLIS);
 
         createTestFileForErrorStats(DEFAULT_TIMESTAMPS_MILLIS);
-        ErrorStats errorStats = new ErrorStats(mSpyContext, mLooper);
+        ErrorStats errorStats = new ErrorStats(mSpyContext, mLooper, false);
 
         verifyTestDataForErrorStats(errorStats.mPulledAtoms, DEFAULT_TIMESTAMPS_MILLIS);
     }
@@ -192,7 +192,7 @@
     @Test
     public void testPullApiStatsLessThanMinPullIntervalShouldSkip() throws Exception {
         createTestFileForApiStats(System.currentTimeMillis() - MIN_PULL_INTERVAL_MILLIS / 2);
-        ApiStats apiStats = spy(new ApiStats(mSpyContext, mLooper));
+        ApiStats apiStats = spy(new ApiStats(mSpyContext, mLooper, false));
         final List<StatsEvent> data = new ArrayList<>();
 
         int result = apiStats.pull(data);
@@ -205,7 +205,7 @@
     @Test
     public void testPullApiStatsGreaterThanMinPullIntervalShouldNotSkip() throws Exception {
         createTestFileForApiStats(System.currentTimeMillis() - MIN_PULL_INTERVAL_MILLIS - 1);
-        ApiStats apiStats = spy(new ApiStats(mSpyContext, mLooper));
+        ApiStats apiStats = spy(new ApiStats(mSpyContext, mLooper, false));
         final List<StatsEvent> data = new ArrayList<>();
         int sizePulled = apiStats.mPulledAtoms.telecomApiStats.length;
 
@@ -220,7 +220,7 @@
     @Test
     public void testPullAudioRouteStatsLessThanMinPullIntervalShouldSkip() throws Exception {
         createTestFileForAudioRouteStats(System.currentTimeMillis() - MIN_PULL_INTERVAL_MILLIS / 2);
-        AudioRouteStats audioRouteStats = spy(new AudioRouteStats(mSpyContext, mLooper));
+        AudioRouteStats audioRouteStats = spy(new AudioRouteStats(mSpyContext, mLooper, false));
         final List<StatsEvent> data = new ArrayList<>();
 
         int result = audioRouteStats.pull(data);
@@ -233,7 +233,7 @@
     @Test
     public void testPullAudioRouteStatsGreaterThanMinPullIntervalShouldNotSkip() throws Exception {
         createTestFileForAudioRouteStats(System.currentTimeMillis() - MIN_PULL_INTERVAL_MILLIS - 1);
-        AudioRouteStats audioRouteStats = spy(new AudioRouteStats(mSpyContext, mLooper));
+        AudioRouteStats audioRouteStats = spy(new AudioRouteStats(mSpyContext, mLooper, false));
         final List<StatsEvent> data = new ArrayList<>();
         int sizePulled = audioRouteStats.mPulledAtoms.callAudioRouteStats.length;
 
@@ -248,7 +248,7 @@
     @Test
     public void testPullCallStatsLessThanMinPullIntervalShouldSkip() throws Exception {
         createTestFileForCallStats(System.currentTimeMillis() - MIN_PULL_INTERVAL_MILLIS / 2);
-        CallStats callStats = spy(new CallStats(mSpyContext, mLooper));
+        CallStats callStats = spy(new CallStats(mSpyContext, mLooper, false));
         final List<StatsEvent> data = new ArrayList<>();
 
         int result = callStats.pull(data);
@@ -261,7 +261,7 @@
     @Test
     public void testPullCallStatsGreaterThanMinPullIntervalShouldNotSkip() throws Exception {
         createTestFileForCallStats(System.currentTimeMillis() - MIN_PULL_INTERVAL_MILLIS - 1);
-        CallStats callStats = spy(new CallStats(mSpyContext, mLooper));
+        CallStats callStats = spy(new CallStats(mSpyContext, mLooper, false));
         final List<StatsEvent> data = new ArrayList<>();
         int sizePulled = callStats.mPulledAtoms.callStats.length;
 
@@ -276,7 +276,7 @@
     @Test
     public void testPullErrorStatsLessThanMinPullIntervalShouldSkip() throws Exception {
         createTestFileForErrorStats(System.currentTimeMillis() - MIN_PULL_INTERVAL_MILLIS / 2);
-        ErrorStats errorStats = spy(new ErrorStats(mSpyContext, mLooper));
+        ErrorStats errorStats = spy(new ErrorStats(mSpyContext, mLooper, false));
         final List<StatsEvent> data = new ArrayList<>();
 
         int result = errorStats.pull(data);
@@ -289,7 +289,7 @@
     @Test
     public void testPullErrorStatsGreaterThanMinPullIntervalShouldNotSkip() throws Exception {
         createTestFileForErrorStats(System.currentTimeMillis() - MIN_PULL_INTERVAL_MILLIS - 1);
-        ErrorStats errorStats = spy(new ErrorStats(mSpyContext, mLooper));
+        ErrorStats errorStats = spy(new ErrorStats(mSpyContext, mLooper, false));
         final List<StatsEvent> data = new ArrayList<>();
         int sizePulled = errorStats.mPulledAtoms.telecomErrorStats.length;
 
@@ -303,7 +303,7 @@
 
     @Test
     public void testApiStatsLogCount() throws Exception {
-        ApiStats apiStats = spy(new ApiStats(mSpyContext, mLooper));
+        ApiStats apiStats = spy(new ApiStats(mSpyContext, mLooper, false));
         ApiStats.ApiEvent event = new ApiStats.ApiEvent(VALUE_API_ID, VALUE_UID, VALUE_API_RESULT);
 
         for (int i = 0; i < 10; i++) {
@@ -384,7 +384,7 @@
         };
         final int[] results = {ApiStats.RESULT_UNKNOWN, ApiStats.RESULT_NORMAL,
                 ApiStats.RESULT_EXCEPTION, ApiStats.RESULT_PERMISSION};
-        ApiStats apiStats = spy(new ApiStats(mSpyContext, mLooper));
+        ApiStats apiStats = spy(new ApiStats(mSpyContext, mLooper, false));
         Random rand = new Random();
         Map<ApiStats.ApiEvent, Integer> eventMap = new HashMap<>();
 
@@ -408,7 +408,7 @@
 
     @Test
     public void testAudioRouteStatsLog() throws Exception {
-        AudioRouteStats audioRouteStats = spy(new AudioRouteStats(mSpyContext, mLooper));
+        AudioRouteStats audioRouteStats = spy(new AudioRouteStats(mSpyContext, mLooper, false));
 
         audioRouteStats.log(VALUE_AUDIO_ROUTE_TYPE1, VALUE_AUDIO_ROUTE_TYPE2, true, false,
                 VALUE_AUDIO_ROUTE_LATENCY);
@@ -436,7 +436,7 @@
     @Test
     public void testAudioRouteStatsOnEnterThenExit() throws Exception {
         int latency = 500;
-        AudioRouteStats audioRouteStats = spy(new AudioRouteStats(mSpyContext, mLooper));
+        AudioRouteStats audioRouteStats = spy(new AudioRouteStats(mSpyContext, mLooper, false));
 
         audioRouteStats.onRouteEnter(mMockPendingAudioRoute);
         waitForHandlerActionDelayed(audioRouteStats, TEST_TIMEOUT, latency);
@@ -466,7 +466,7 @@
         int delay = 100;
         int latency = 500;
         int duration = 1000;
-        AudioRouteStats audioRouteStats = spy(new AudioRouteStats(mSpyContext, mLooper));
+        AudioRouteStats audioRouteStats = spy(new AudioRouteStats(mSpyContext, mLooper, false));
 
         audioRouteStats.onRouteEnter(mMockPendingAudioRoute);
         waitForHandlerActionDelayed(audioRouteStats, TEST_TIMEOUT, latency);
@@ -502,7 +502,7 @@
     public void testAudioRouteStatsOnRevertToSourceBeyondThreshold() throws Exception {
         int delay = 100;
         int latency = 500;
-        AudioRouteStats audioRouteStats = spy(new AudioRouteStats(mSpyContext, mLooper));
+        AudioRouteStats audioRouteStats = spy(new AudioRouteStats(mSpyContext, mLooper, false));
 
         audioRouteStats.onRouteEnter(mMockPendingAudioRoute);
         waitForHandlerActionDelayed(audioRouteStats, TEST_TIMEOUT, latency);
@@ -540,7 +540,7 @@
         int delay = 100;
         int latency = 500;
         int duration = 1000;
-        AudioRouteStats audioRouteStats = spy(new AudioRouteStats(mSpyContext, mLooper));
+        AudioRouteStats audioRouteStats = spy(new AudioRouteStats(mSpyContext, mLooper, false));
 
         audioRouteStats.onRouteEnter(mMockPendingAudioRoute);
         waitForHandlerActionDelayed(audioRouteStats, TEST_TIMEOUT, latency);
@@ -575,7 +575,7 @@
     @Test
     public void testAudioRouteStatsOnMultipleEnterWithoutExit() throws Exception {
         int latency = 500;
-        AudioRouteStats audioRouteStats = spy(new AudioRouteStats(mSpyContext, mLooper));
+        AudioRouteStats audioRouteStats = spy(new AudioRouteStats(mSpyContext, mLooper, false));
 
         audioRouteStats.onRouteEnter(mMockPendingAudioRoute);
         waitForHandlerActionDelayed(audioRouteStats, TEST_TIMEOUT, latency);
@@ -596,7 +596,7 @@
     @Test
     public void testAudioRouteStatsOnMultipleEnterWithExit() throws Exception {
         int latency = 500;
-        AudioRouteStats audioRouteStats = spy(new AudioRouteStats(mSpyContext, mLooper));
+        AudioRouteStats audioRouteStats = spy(new AudioRouteStats(mSpyContext, mLooper, false));
 
         audioRouteStats.onRouteEnter(mMockPendingAudioRoute);
         waitForHandlerActionDelayed(audioRouteStats, TEST_TIMEOUT, latency);
@@ -619,7 +619,7 @@
     @Test
     public void testAudioRouteStatsOnRouteToSameDestWithExit() throws Exception {
         int latency = 500;
-        AudioRouteStats audioRouteStats = spy(new AudioRouteStats(mSpyContext, mLooper));
+        AudioRouteStats audioRouteStats = spy(new AudioRouteStats(mSpyContext, mLooper, false));
         doReturn(mMockSourceRoute).when(mMockPendingAudioRoute).getDestRoute();
 
         audioRouteStats.onRouteEnter(mMockPendingAudioRoute);
@@ -640,7 +640,7 @@
 
     @Test
     public void testCallStatsLog() throws Exception {
-        CallStats callStats = spy(new CallStats(mSpyContext, mLooper));
+        CallStats callStats = spy(new CallStats(mSpyContext, mLooper, false));
 
         callStats.log(VALUE_CALL_DIRECTION, false, false, true, VALUE_CALL_ACCOUNT_TYPE,
                 VALUE_UID, VALUE_CALL_DURATION);
@@ -688,7 +688,7 @@
         doReturn(true).when(account).hasCapabilities(eq(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION));
         doReturn(callingPackage).when(call).getCallingPackageIdentity();
         doReturn(handle).when(call).getTargetPhoneAccount();
-        CallStats callStats = spy(new CallStats(mSpyContext, mLooper));
+        CallStats callStats = spy(new CallStats(mSpyContext, mLooper, false));
 
         callStats.onCallStart(call);
         waitForHandlerAction(callStats, TEST_TIMEOUT);
@@ -726,7 +726,7 @@
         doReturn(true).when(account).hasCapabilities(eq(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION));
         doReturn(callingPackage).when(call).getCallingPackageIdentity();
         doReturn(handle).when(call).getTargetPhoneAccount();
-        CallStats callStats = spy(new CallStats(mSpyContext, mLooper));
+        CallStats callStats = spy(new CallStats(mSpyContext, mLooper, false));
 
         callStats.onCallStart(call);
         waitForHandlerAction(callStats, TEST_TIMEOUT);
@@ -744,7 +744,7 @@
 
     @Test
     public void testErrorStatsLogCount() throws Exception {
-        ErrorStats errorStats = spy(new ErrorStats(mSpyContext, mLooper));
+        ErrorStats errorStats = spy(new ErrorStats(mSpyContext, mLooper, false));
         for (int i = 0; i < 10; i++) {
             errorStats.log(VALUE_MODULE_ID, VALUE_ERROR_ID);
             waitForHandlerAction(errorStats, TEST_TIMEOUT);
@@ -760,7 +760,7 @@
 
     @Test
     public void testErrorStatsLogEvent() throws Exception {
-        ErrorStats errorStats = spy(new ErrorStats(mSpyContext, mLooper));
+        ErrorStats errorStats = spy(new ErrorStats(mSpyContext, mLooper, false));
         int[] modules = {
                 ErrorStats.SUB_UNKNOWN,
                 ErrorStats.SUB_CALL_AUDIO,
@@ -823,6 +823,54 @@
         }
     }
 
+    @Test
+    public void testApiStatsWithTestModeOn() throws Exception {
+        final List<StatsEvent> data = new ArrayList<>();
+        ApiStats apiStats = spy(new ApiStats(mSpyContext, mLooper, true));
+        apiStats.pull(data);
+        apiStats.flush();
+
+        verify(mSpyContext, never()).getFileStreamPath(anyString());
+        verify(apiStats, times(1)).onPull(any());
+        verify(mSpyContext, never()).openFileOutput(anyString(), anyInt());
+    }
+
+    @Test
+    public void testAudioRouteStatsWithTestModeOn() throws Exception {
+        final List<StatsEvent> data = new ArrayList<>();
+        AudioRouteStats audioRouteStats = spy(new AudioRouteStats(mSpyContext, mLooper, true));
+        audioRouteStats.pull(data);
+        audioRouteStats.flush();
+
+        verify(mSpyContext, never()).getFileStreamPath(anyString());
+        verify(audioRouteStats, times(1)).onPull(any());
+        verify(mSpyContext, never()).openFileOutput(anyString(), anyInt());
+    }
+
+    @Test
+    public void testCallStatsWithTestModeOn() throws Exception {
+        final List<StatsEvent> data = new ArrayList<>();
+        CallStats callStats = spy(new CallStats(mSpyContext, mLooper, true));
+        callStats.pull(data);
+        callStats.flush();
+
+        verify(mSpyContext, never()).getFileStreamPath(anyString());
+        verify(callStats, times(1)).onPull(any());
+        verify(mSpyContext, never()).openFileOutput(anyString(), anyInt());
+    }
+
+    @Test
+    public void testErrorStatsWithTestModeOn() throws Exception {
+        final List<StatsEvent> data = new ArrayList<>();
+        ErrorStats errorStats = spy(new ErrorStats(mSpyContext, mLooper, true));
+        errorStats.pull(data);
+        errorStats.flush();
+
+        verify(mSpyContext, never()).getFileStreamPath(anyString());
+        verify(errorStats, times(1)).onPull(any());
+        verify(mSpyContext, never()).openFileOutput(anyString(), anyInt());
+    }
+
     private void createTestFileForApiStats(long timestamps) throws IOException {
         PulledAtomsClass.PulledAtoms atom = new PulledAtomsClass.PulledAtoms();
         atom.telecomApiStats =
diff --git a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
index 7d01a44..d002739 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
@@ -1096,9 +1096,10 @@
     @Test
     public void testRegisterPhoneAccountImageIconCrossUser() throws RemoteException {
         String packageNameToUse = "com.android.officialpackage";
+        String callingUserId = String.valueOf(Binder.getCallingUserHandle().getIdentifier());
         PhoneAccountHandle phHandle = new PhoneAccountHandle(new ComponentName(
                 packageNameToUse, "cs"), "test", Binder.getCallingUserHandle());
-        Icon icon = Icon.createWithContentUri("content://10@media/external/images/media/");
+        Icon icon = Icon.createWithContentUri("content://12@media/external/images/media/");
         PhoneAccount phoneAccount = makePhoneAccount(phHandle).setIcon(icon).build();
         doReturn(PackageManager.PERMISSION_GRANTED)
                 .when(mContext).checkCallingOrSelfPermission(MODIFY_PHONE_STATE);
@@ -1108,19 +1109,21 @@
 
         icon = Icon.createWithContentUri(
                 new Uri.Builder().scheme("content")
-                        .encodedAuthority("10%40media")
+                        .encodedAuthority("12%40media")
                         .path("external/images/media/${mediaId.text}".trim())
                         .build());
         phoneAccount = makePhoneAccount(phHandle).setIcon(icon).build();
         // This should fail; security exception will be thrown
         registerPhoneAccountTestHelper(phoneAccount, false);
 
-        icon = Icon.createWithContentUri( Uri.parse("content://10%40play.ground"));
+        icon = Icon.createWithContentUri( Uri.parse("content://12%40play.ground"));
         phoneAccount = makePhoneAccount(phHandle).setIcon(icon).build();
         // This should fail; security exception will be thrown
         registerPhoneAccountTestHelper(phoneAccount, false);
 
-        icon = Icon.createWithContentUri("content://0@media/external/images/media/");
+        // Generate a URI referencing the calling/current user ID:
+        String currentUserUri = "content://" + callingUserId + "@media/external/images/media/";
+        icon = Icon.createWithContentUri(currentUserUri);
         phoneAccount = makePhoneAccount(phHandle).setIcon(icon).build();
         // This should succeed.
         registerPhoneAccountTestHelper(phoneAccount, true);
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index 447e5ab..0b4cb2d 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -592,7 +592,8 @@
                 Runnable::run,
                 mBlockedNumbersAdapter,
                 mFeatureFlags,
-                mTelephonyFlags);
+                mTelephonyFlags,
+                mHandlerThread.getLooper());
 
         mComponentContextFixture.setTelecomManager(new TelecomManager(
                 mComponentContextFixture.getTestDouble(),
diff --git a/tests/src/com/android/server/telecom/tests/TelecomTestCase.java b/tests/src/com/android/server/telecom/tests/TelecomTestCase.java
index 5b5c3ed..bfe18f6 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomTestCase.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomTestCase.java
@@ -56,6 +56,7 @@
     }
 
     public void tearDown() throws Exception {
+        mComponentContextFixture.destroy();
         mComponentContextFixture = null;
         mMockitoHelper.tearDown();
         Mockito.framework().clearInlineMocks();
diff --git a/tests/src/com/android/server/telecom/tests/TransactionTests.java b/tests/src/com/android/server/telecom/tests/TransactionTests.java
index 0a23913..6c049f6 100644
--- a/tests/src/com/android/server/telecom/tests/TransactionTests.java
+++ b/tests/src/com/android/server/telecom/tests/TransactionTests.java
@@ -63,6 +63,7 @@
 import com.android.server.telecom.PhoneNumberUtilsAdapter;
 import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.callsequencing.CallTransactionResult;
+import com.android.server.telecom.callsequencing.CallsManagerCallSequencingAdapter;
 import com.android.server.telecom.callsequencing.TransactionManager;
 import com.android.server.telecom.callsequencing.VerifyCallStateChangeTransaction;
 import com.android.server.telecom.callsequencing.voip.EndCallTransaction;
@@ -97,6 +98,7 @@
     @Mock private Call mMockCall1;
     @Mock private Context mMockContext;
     @Mock private CallsManager mCallsManager;
+    @Mock private CallsManagerCallSequencingAdapter mCallSequencingAdapter;
     @Mock private ToastFactory mToastFactory;
     @Mock private ClockProxy mClockProxy;
     @Mock private PhoneNumberUtilsAdapter mPhoneNumberUtilsAdapter;
@@ -113,6 +115,7 @@
         MockitoAnnotations.initMocks(this);
         Mockito.when(mMockCall1.getId()).thenReturn(CALL_ID_1);
         Mockito.when(mMockContext.getResources()).thenReturn(Mockito.mock(Resources.class));
+        when(mCallsManager.getCallSequencingAdapter()).thenReturn(mCallSequencingAdapter);
     }
 
     @Override
@@ -220,7 +223,7 @@
         transaction.processTransaction(null);
 
         // THEN
-        verify(mCallsManager, times(1))
+        verify(mCallsManager.getCallSequencingAdapter(), times(1))
                 .transactionHoldPotentialActiveCallForNewCall(eq(mMockCall1), eq(false),
                         isA(OutcomeReceiver.class));
     }
diff --git a/tests/src/com/android/server/telecom/tests/TransactionalServiceWrapperTest.java b/tests/src/com/android/server/telecom/tests/TransactionalServiceWrapperTest.java
index fea6135..16b6e44 100644
--- a/tests/src/com/android/server/telecom/tests/TransactionalServiceWrapperTest.java
+++ b/tests/src/com/android/server/telecom/tests/TransactionalServiceWrapperTest.java
@@ -34,6 +34,7 @@
 
 import com.android.internal.telecom.ICallControl;
 import com.android.internal.telecom.ICallEventCallback;
+import com.android.server.telecom.AnomalyReporterAdapter;
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallsManager;
 import com.android.server.telecom.TelecomSystem;
@@ -70,6 +71,7 @@
     @Mock private TransactionManager mTransactionManager;
     @Mock private ICallEventCallback mCallEventCallback;
     @Mock private TransactionalServiceRepository mRepository;
+    @Mock private AnomalyReporterAdapter mAnomalyReporterAdapter;
     @Mock private IBinder mIBinder;
     private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() {};
 
@@ -84,7 +86,7 @@
         Mockito.when(mCallEventCallback.asBinder()).thenReturn(mIBinder);
         mTransactionalServiceWrapper = new TransactionalServiceWrapper(mCallEventCallback,
                 mCallsManager, SERVICE_HANDLE, mMockCall1, mRepository, mTransactionManager,
-                false /*call sequencing*/);
+                false /*call sequencing*/, mFeatureFlags, mAnomalyReporterAdapter);
     }
 
     @Override
@@ -98,7 +100,7 @@
         TransactionalServiceWrapper service =
                 new TransactionalServiceWrapper(mCallEventCallback,
                         mCallsManager, SERVICE_HANDLE, mMockCall1, mRepository, mTransactionManager,
-                        false /*call sequencing*/);
+                        false /*call sequencing*/, mFeatureFlags, mAnomalyReporterAdapter);
 
         assertEquals(SERVICE_HANDLE, service.getPhoneAccountHandle());
         assertEquals(1, service.getNumberOfTrackedCalls());
@@ -109,7 +111,7 @@
         TransactionalServiceWrapper service =
                 new TransactionalServiceWrapper(mCallEventCallback,
                         mCallsManager, SERVICE_HANDLE, mMockCall1, mRepository, mTransactionManager,
-                        false /*call sequencing*/);
+                        false /*call sequencing*/, mFeatureFlags, mAnomalyReporterAdapter);
 
         assertEquals(1, service.getNumberOfTrackedCalls());
         service.trackCall(mMockCall2);
diff --git a/tests/src/com/android/server/telecom/tests/VoipCallMonitorTest.java b/tests/src/com/android/server/telecom/tests/VoipCallMonitorTest.java
index e6d1bc9..1b3856c 100644
--- a/tests/src/com/android/server/telecom/tests/VoipCallMonitorTest.java
+++ b/tests/src/com/android/server/telecom/tests/VoipCallMonitorTest.java
@@ -22,10 +22,16 @@
 import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -38,6 +44,7 @@
 import android.content.Intent;
 import android.content.ServiceConnection;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.UserHandle;
 import android.service.notification.StatusBarNotification;
@@ -52,12 +59,16 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
 @RunWith(JUnit4.class)
 public class VoipCallMonitorTest extends TelecomTestCase {
     private VoipCallMonitor mMonitor;
@@ -70,6 +81,7 @@
     private static final UserHandle USER_HANDLE_1 = new UserHandle(1);
     private static final long TIMEOUT = 6000L;
 
+    @Mock private Handler mHandler;
     @Mock private TelecomSystem.SyncRoot mLock;
     @Mock private ActivityManagerInternal mActivityManagerInternal;
     @Mock private IBinder mServiceConnection;
@@ -83,7 +95,8 @@
     @Before
     public void setUp() throws Exception {
         super.setUp();
-        mMonitor = new VoipCallMonitor(mContext, mLock);
+        mHandler = mock(Handler.class);
+        mMonitor = new VoipCallMonitor(mContext, mHandler, mLock);
         mActivityManagerInternal = mock(ActivityManagerInternal.class);
         mMonitor.setActivityManagerInternal(mActivityManagerInternal);
         mMonitor.registerNotificationListener();
@@ -124,21 +137,47 @@
         mMonitor.onCallRemoved(call);
     }
 
+    /**
+     * Tests that {@link VoipCallMonitor#stopFGSDelegation} does not throw a NullPointerException
+     * when called on a transactional call that has not been tracked by the account to calls
+     * mapping, and that no calls are made to ActivityManagerInternal.stopForegroundServiceDelegate.
+     */
+    @SmallTest
+    @Test
+    public void testStopFgsDelegationWithoutAnyTrackedCalls() {
+        //GIVEN: a transactional call that has NOT been added to the monitor tracking
+        Call call = createTestCall("testCall", mHandle1User1);
+        ConcurrentHashMap<PhoneAccountHandle, Set<Call>> m = mMonitor.getAccountToCallsMapping();
+        assertEquals(0, m.size());
+        assertNull(m.get(mHandle1User1));
+
+        // WHEN: stop is called on the transactional call
+        mMonitor.stopFGSDelegation(call, mHandle1User1);
+
+        // THEN: a NullPointerException should not be thrown at runtime
+        verify(mActivityManagerInternal, times(0))
+                .stopForegroundServiceDelegate(any(ServiceConnection.class));
+        assertEquals(0, m.size());
+        assertNull(m.get(mHandle1User1));
+    }
+
     @SmallTest
     @Test
     public void testStartMonitorForOneCall() {
         // GIVEN - a single call and notification for a voip app
         Call call = createTestCall("testCall", mHandle1User1);
-        StatusBarNotification sbn = createStatusBarNotificationFromHandle(mHandle1User1);
+        StatusBarNotification sbn = createStatusBarNotificationFromHandle(mHandle1User1, 1);
 
         // WHEN - the Voip call is added and a notification is posted, verify FGS is gained
         addCallAndVerifyFgsIsGained(call);
         mMonitor.postNotification(sbn);
+        assertNotificationTimeoutTriggered();
+        assertFalse(mMonitor.getNewCallsMissingCallStyleNotificationQueue().contains(call));
 
         // THEN - when the Voip call is removed, verify that FGS is revoked for the app
         mMonitor.onCallRemoved(call);
         mMonitor.removeNotification(sbn);
-        verify(mActivityManagerInternal, timeout(TIMEOUT))
+        verify(mActivityManagerInternal, times(1))
                 .stopForegroundServiceDelegate(any(ServiceConnection.class));
     }
 
@@ -150,25 +189,34 @@
     public void testStopDelegation_SameApp() {
         // GIVEN - 2 consecutive calls for a single Voip app
         Call call1 = createTestCall("testCall1", mHandle1User1);
-        StatusBarNotification sbn1 = createStatusBarNotificationFromHandle(mHandle1User1);
+        StatusBarNotification sbn1 = createStatusBarNotificationFromHandle(mHandle1User1, 1);
         Call call2 = createTestCall("testCall2", mHandle1User1);
-        StatusBarNotification sbn2 = createStatusBarNotificationFromHandle(mHandle1User1);
+        StatusBarNotification sbn2 = createStatusBarNotificationFromHandle(mHandle1User1, 2);
 
         // WHEN - the second call is added and the first is disconnected
-        mMonitor.postNotification(sbn1);
+        // -- add the first all and post the corresponding notification
         addCallAndVerifyFgsIsGained(call1);
-        mMonitor.postNotification(sbn2);
+        assertTrue(mMonitor.getNewCallsMissingCallStyleNotificationQueue().contains(call1));
+        mMonitor.postNotification(sbn1);
+        assertNotificationTimeoutTriggered();
+        assertFalse(mMonitor.getNewCallsMissingCallStyleNotificationQueue().contains(call1));
+        // -- add the second call and post the corresponding notification
         mMonitor.onCallAdded(call2);
-        mMonitor.onCallRemoved(call1);
+        assertTrue(mMonitor.getNewCallsMissingCallStyleNotificationQueue().contains(call2));
+        mMonitor.postNotification(sbn2);
+        assertNotificationTimeoutTriggered();
+        assertFalse(mMonitor.getNewCallsMissingCallStyleNotificationQueue().contains(call2));
 
         // THEN - assert FGS is maintained for the process since there is still an ongoing call
-        verify(mActivityManagerInternal, timeout(TIMEOUT).times(0))
-                .stopForegroundServiceDelegate(any(ServiceConnection.class));
+        mMonitor.onCallRemoved(call1);
         mMonitor.removeNotification(sbn1);
+        assertNotificationTimeoutTriggered();
+        verify(mActivityManagerInternal, times(0))
+                .stopForegroundServiceDelegate(any(ServiceConnection.class));
         // once all calls are removed, verify FGS is stopped
         mMonitor.onCallRemoved(call2);
         mMonitor.removeNotification(sbn2);
-        verify(mActivityManagerInternal, timeout(TIMEOUT).times(1))
+        verify(mActivityManagerInternal, times(1))
                 .stopForegroundServiceDelegate(any(ServiceConnection.class));
     }
 
@@ -216,9 +264,10 @@
      */
     @SmallTest
     @Test
+    @Ignore("b/383403913") // when b/383403913 is fixed, remove the @Ignore
     public void testStopFgsIfCallNotificationIsRemoved_PostedAfterFgsIsGained() {
         // GIVEN
-        StatusBarNotification sbn = createStatusBarNotificationFromHandle(mHandle1User1);
+        StatusBarNotification sbn = createStatusBarNotificationFromHandle(mHandle1User1, 1);
 
         // WHEN
         // FGS is gained after the call is added to VoipCallMonitor
@@ -230,7 +279,65 @@
         // shortly after posting the notification, simulate the user dismissing it
         mMonitor.removeNotification(sbn);
         // FGS should be removed once the notification is removed
-        verify(mActivityManagerInternal, timeout(TIMEOUT)).stopForegroundServiceDelegate(c);
+        assertNotificationTimeoutTriggered();
+        verify(mActivityManagerInternal, times(1)).stopForegroundServiceDelegate(c);
+    }
+
+
+    /**
+     * Tests the behavior of foreground service (FGS) delegation for a VoIP app during a scenario
+     * with two consecutive calls.  In this scenario, the first call is disconnected shortly after
+     * being created but the second call continues.  The apps foreground service should be
+     * maintained.
+     *
+     * GIVEN: Two calls (call1 and call2) are created for the same VoIP app.
+     * WHEN:
+     *  - call1 is added, starting the FGS.
+     *  - call2 is added immediately after.
+     *  - call1 is removed.
+     *  - call1 notification is finally posted (late)
+     *  - call1 notification is removed shortly after since the call was disconnected
+     * THEN:
+     *  - Verifies that the FGS is NOT stopped while call2 is still active.
+     *  - Verifies that the FGS IS stopped after call2 is removed and its notification is gone.
+     */
+    @SmallTest
+    @Test
+    public void test2CallsInQuickSuccession() {
+        // GIVEN - 2 consecutive calls for a single Voip app
+        Call call1 = createTestCall("testCall1", mHandle1User1);
+        StatusBarNotification sbn1 = createStatusBarNotificationFromHandle(mHandle1User1, 1);
+        Call call2 = createTestCall("testCall2", mHandle1User1);
+        StatusBarNotification sbn2 = createStatusBarNotificationFromHandle(mHandle1User1, 2);
+
+        // WHEN - add the calls to the VoipCallMonitor class
+        addCallAndVerifyFgsIsGained(call1);
+        mMonitor.onCallAdded(call2);
+        assertTrue(mMonitor.getNewCallsMissingCallStyleNotificationQueue().contains(call1));
+        assertTrue(mMonitor.getNewCallsMissingCallStyleNotificationQueue().contains(call2));
+        // -- mock the app disconnecting the first
+        mMonitor.onCallRemoved(call1);
+        // Shortly after, simulate the notification updates coming in to the class
+        // -- post and remove the first call-style notification
+        mMonitor.postNotification(sbn1);
+        assertFalse(mMonitor.getNewCallsMissingCallStyleNotificationQueue().contains(call1));
+        mMonitor.removeNotification(sbn1);
+        assertNotificationTimeoutTriggered();
+
+        // -- keep the second notification up since the call will continue
+        mMonitor.postNotification(sbn2);
+        assertFalse(mMonitor.getNewCallsMissingCallStyleNotificationQueue().contains(call2));
+
+        // THEN - assert FGS is maintained for the process since there is still an ongoing call
+        assertNotificationTimeoutTriggered();
+        verify(mActivityManagerInternal, times(0))
+                .stopForegroundServiceDelegate(any(ServiceConnection.class));
+
+        // once all calls are removed, verify FGS is stopped
+        mMonitor.onCallRemoved(call2);
+        mMonitor.removeNotification(sbn2);
+        verify(mActivityManagerInternal, timeout(TIMEOUT).times(1))
+                .stopForegroundServiceDelegate(any(ServiceConnection.class));
     }
 
     /**
@@ -262,9 +369,10 @@
                 .build();
     }
 
-    private StatusBarNotification createStatusBarNotificationFromHandle(PhoneAccountHandle handle) {
+    private StatusBarNotification createStatusBarNotificationFromHandle(
+            PhoneAccountHandle handle, int id) {
         return new StatusBarNotification(
-                handle.getComponentName().getPackageName(), "", 0, "", 0, 0,
+                handle.getComponentName().getPackageName(), "", id, "", 0, 0,
                 createCallStyleNotification(), handle.getUserHandle(), "", 0);
     }
 
@@ -285,4 +393,17 @@
                 mServiceConnection);
         return serviceConnection;
     }
+
+    /**
+     * Verifies that a delayed runnable is posted to the handler to handle the notification timeout.
+     * This also executes the captured runnable to simulate the timeout occurring.
+     */
+    private void assertNotificationTimeoutTriggered() {
+        ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+        verify(mHandler, atLeastOnce()).postDelayed(
+                runnableCaptor.capture(),
+                eq(VoipCallMonitor.NOTIFICATION_NOT_POSTED_IN_TIME_TIMEOUT));
+        Runnable capturedRunnable = runnableCaptor.getValue();
+        capturedRunnable.run();
+    }
 }