Merge "Support TOGGLE_MUTE in CallAudioRouteController" into main
diff --git a/flags/telecom_anomaly_report_flags.aconfig b/flags/telecom_anomaly_report_flags.aconfig
index b060ed0..5d42b86 100644
--- a/flags/telecom_anomaly_report_flags.aconfig
+++ b/flags/telecom_anomaly_report_flags.aconfig
@@ -16,3 +16,14 @@
   description: "If a self-managed call is stuck in certain states, disconnect it"
   bug: "360298368"
 }
+
+# OWNER=tgunn TARGET=25Q2
+flag {
+  name: "dont_timeout_destroyed_calls"
+  namespace: "telecom"
+  description: "When create connection timeout is hit, if call is already destroyed, skip anomaly"
+  bug: "381684580"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/flags/telecom_api_flags.aconfig b/flags/telecom_api_flags.aconfig
index 75efdfa..2dfd878 100644
--- a/flags/telecom_api_flags.aconfig
+++ b/flags/telecom_api_flags.aconfig
@@ -73,3 +73,12 @@
   description: "Formalizes the getLastKnownCellIdentity API that Telecom reliees on as a system api"
   bug: "327454165"
 }
+
+# OWNER=grantmenke TARGET=25Q2
+flag {
+  name: "allow_system_apps_resolve_voip_calls"
+  is_exported: true
+  namespace: "telecom"
+  description: "Allow system apps such as accessibility to accept and end VOIP calls."
+  bug: "353579043"
+}
diff --git a/flags/telecom_bluetoothdevicemanager_flags.aconfig b/flags/telecom_bluetoothdevicemanager_flags.aconfig
index 4c91491..1c8bd0c 100644
--- a/flags/telecom_bluetoothdevicemanager_flags.aconfig
+++ b/flags/telecom_bluetoothdevicemanager_flags.aconfig
@@ -8,3 +8,23 @@
   description: "Fix for Log.wtf in the BinderProxy"
   bug: "333417369"
 }
+# OWNER=huiwang TARGET=25Q1
+flag {
+  name: "keep_bluetooth_devices_cache_updated"
+  namespace: "telecom"
+  description: "Fix the devices cache issue of BluetoothDeviceManager"
+  bug: "380320985"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+# OWNER=grantmenke TARGET=25Q2
+flag {
+  name: "skip_baseline_switch_when_route_not_bluetooth"
+  namespace: "telecom"
+  description: "Only switch back to baseline if the call audio is currently routed to bluetooth"
+  bug: "333417369"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
\ No newline at end of file
diff --git a/flags/telecom_callaudioroutestatemachine_flags.aconfig b/flags/telecom_callaudioroutestatemachine_flags.aconfig
index a60c0f1..e86db31 100644
--- a/flags/telecom_callaudioroutestatemachine_flags.aconfig
+++ b/flags/telecom_callaudioroutestatemachine_flags.aconfig
@@ -89,6 +89,17 @@
   bug: "315865533"
 }
 
+# OWNER=tgunn TARGET=24Q3
+flag {
+  name: "dont_use_communication_device_tracker"
+  namespace: "telecom"
+  description: "Do not use the communication device tracker with useRefactoredAudioRouteSwitching."
+  bug: "346472575"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
 # OWNER=pmadapurmath TARGET=24Q3
 flag {
   name: "resolve_switching_bt_devices_computation"
@@ -129,3 +140,14 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+# OWNER=tgunn TARGET=25Q2
+flag {
+  name: "only_clear_communication_device_on_inactive"
+  namespace: "telecom"
+  description: "Only clear the communication device when transitioning to an inactive route."
+  bug: "376781369"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/flags/telecom_headless_system_user_mode.aconfig b/flags/telecom_headless_system_user_mode.aconfig
index d9636a0..4135794 100644
--- a/flags/telecom_headless_system_user_mode.aconfig
+++ b/flags/telecom_headless_system_user_mode.aconfig
@@ -23,4 +23,16 @@
     metadata {
         purpose: PURPOSE_BUGFIX
       }
+}
+
+# OWNER=grantmenke TARGET=25Q2
+flag {
+    name: "telecom_app_label_proxy_hsum_aware"
+    is_exported: true
+    namespace: "telecom"
+    description: "Support HSUM mode by ensuring AppLableProxy is multiuser aware."
+    bug: "321817633"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+      }
 }
\ No newline at end of file
diff --git a/flags/telecom_resolve_hidden_dependencies.aconfig b/flags/telecom_resolve_hidden_dependencies.aconfig
index a120b85..e5bb1fb 100644
--- a/flags/telecom_resolve_hidden_dependencies.aconfig
+++ b/flags/telecom_resolve_hidden_dependencies.aconfig
@@ -16,4 +16,5 @@
     description: "Fixed read only flag used for setting up BlockedNumbersManager to be retrieved via context"
     bug: "325049252"
     is_fixed_read_only: true
+    is_exported: true
 }
diff --git a/flags/telecom_ringer_flag_declarations.aconfig b/flags/telecom_ringer_flag_declarations.aconfig
index 6517e0f..f954b09 100644
--- a/flags/telecom_ringer_flag_declarations.aconfig
+++ b/flags/telecom_ringer_flag_declarations.aconfig
@@ -15,4 +15,16 @@
   namespace: "telecom"
   description: "Gates whether to ensure that when a user is in their car, they are able to hear ringing for an incoming call."
   bug: "348708398"
+}
+
+
+# OWNER=tjstuart TARGET=25Q1
+flag {
+  name: "get_ringer_mode_anom_report"
+  namespace: "telecom"
+  description: "getRingerMode & getRingerModeInternal should return the same val when dnd is off"
+  bug: "307389562"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
 }
\ No newline at end of file
diff --git a/res/values-ne/strings.xml b/res/values-ne/strings.xml
index d8fc473..4aeceef 100644
--- a/res/values-ne/strings.xml
+++ b/res/values-ne/strings.xml
@@ -72,7 +72,7 @@
     <string name="block_button" msgid="485080149164258770">"रोक्नुहोस्"</string>
     <string name="non_primary_user" msgid="315564589279622098">"यन्त्रको मालिकले रोकिएका नम्बरहरूलाई हेर्न र व्यवस्थापन गर्न सक्छ।"</string>
     <string name="delete_icon_description" msgid="5335959254954774373">"अनब्लक गर्नुहोस्"</string>
-    <string name="blocked_numbers_butter_bar_title" msgid="582982373755950791">"रोक लगाउने काम अस्थायी रूपमा निष्क्रिय छ"</string>
+    <string name="blocked_numbers_butter_bar_title" msgid="582982373755950791">"रोक लगाउने काम अस्थायी रूपमा अफ छ"</string>
     <string name="blocked_numbers_butter_bar_body" msgid="1261213114919301485">"तपाईँले आपत्‌कालीन नम्बरमा डायल गरेपछि वा टेक्स्ट म्यासेज पठाएपछि आपत्‌कालीन सेवाहरूले तपाईँलाई सम्पर्क गर्न सकून् भन्ने कुरा सुनिश्चित गर्न कलमाथिको अवरोध निष्क्रिय गरिन्छ।"</string>
     <string name="blocked_numbers_butter_bar_button" msgid="2704456308072489793">"अब पुन:-अन गर्नुहोस्"</string>
     <string name="blocked_numbers_number_blocked_message" msgid="4314736791180919167">"<xliff:g id="BLOCKED_NUMBER">%1$s</xliff:g> माथि रोक लगाइयो"</string>
diff --git a/res/values-pa/strings.xml b/res/values-pa/strings.xml
index 831f260..96ee0e8 100644
--- a/res/values-pa/strings.xml
+++ b/res/values-pa/strings.xml
@@ -109,7 +109,7 @@
     <string name="phone_settings_call_blocking_txt" msgid="7311523114822507178">"ਕਾਲ ਬਲਾਕ ਕਰਨਾ"</string>
     <string name="phone_settings_number_not_in_contact_txt" msgid="2602249106007265757">"ਨੰਬਰ ਜੋ ਤੁਹਾਡੇ ਸੰਪਰਕਾਂ ਵਿੱਚ ਨਹੀਂ ਹਨ"</string>
     <string name="phone_settings_number_not_in_contact_summary_txt" msgid="963327038085718969">"ਉਹ ਨੰਬਰ ਬਲਾਕ ਕਰੋ ਜੋ ਤੁਹਾਡੇ ਸੰਪਰਕਾਂ ਵਿੱਚ ਨਹੀਂ ਹਨ"</string>
-    <string name="phone_settings_private_num_txt" msgid="6339272760338475619">"ਨਿੱਜੀ"</string>
+    <string name="phone_settings_private_num_txt" msgid="6339272760338475619">"ਪ੍ਰਾਈਵੇਟ"</string>
     <string name="phone_settings_private_num_summary_txt" msgid="6755758240544021037">"ਉਹ ਕਾਲਰ ਬਲਾਕ ਕਰੋ ਜਿਨ੍ਹਾਂ ਦਾ ਨੰਬਰ ਨਹੀਂ ਦਿਖਾਈ ਦਿੰਦਾ ਹੈ"</string>
     <string name="phone_settings_payphone_txt" msgid="5003987966052543965">"ਜਨਤਕ ਫ਼ੋਨ"</string>
     <string name="phone_settings_payphone_summary_txt" msgid="3936631076065563665">"ਜਨਤਕ ਫ਼ੋਨਾਂ ਵਾਲੀਆਂ ਕਾਲਾਂ ਬਲਾਕ ਕਰੋ"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 1e8b027..e302ea6 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -50,8 +50,8 @@
     <string name="outgoing_call_not_allowed_no_permission" msgid="8590468836581488679">"Este aplicativo não pode fazer chamadas sem a permissão do smartphone."</string>
     <string name="outgoing_call_error_no_phone_number_supplied" msgid="7665135102566099778">"Para realizar uma chamada, digite um número válido."</string>
     <string name="duplicate_video_call_not_allowed" msgid="5754746140185781159">"No momento, não é possível adicionar a chamada."</string>
-    <string name="no_vm_number" msgid="2179959110602180844">"Número correio de voz ausente"</string>
-    <string name="no_vm_number_msg" msgid="1339245731058529388">"Não há um número correio de voz armazenado no chip."</string>
+    <string name="no_vm_number" msgid="2179959110602180844">"Número do correio de voz ausente"</string>
+    <string name="no_vm_number_msg" msgid="1339245731058529388">"Não há um número do correio de voz armazenado no chip."</string>
     <string name="add_vm_number_str" msgid="5179510133063168998">"Adicionar número"</string>
     <string name="change_default_dialer_dialog_title" msgid="5861469279421508060">"Usar o <xliff:g id="NEW_APP">%s</xliff:g> como seu app de telefone padrão?"</string>
     <string name="change_default_dialer_dialog_affirmative" msgid="8604665314757739550">"Definir padrão"</string>
diff --git a/src/com/android/server/telecom/AppLabelProxy.java b/src/com/android/server/telecom/AppLabelProxy.java
index 7c00f28..c4d83dd 100644
--- a/src/com/android/server/telecom/AppLabelProxy.java
+++ b/src/com/android/server/telecom/AppLabelProxy.java
@@ -16,8 +16,11 @@
 
 package com.android.server.telecom;
 
+import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import com.android.server.telecom.flags.FeatureFlags;
+import android.os.UserHandle;
 import android.telecom.Log;
 
 /**
@@ -30,15 +33,34 @@
     class Util {
         /**
          * Default impl of getAppLabel.
-         * @param pm PackageManager instance
+         * @param context Context instance that is not necessarily associated with the correct user.
+         * @param userHandle UserHandle instance of the user that is associated with the app.
          * @param packageName package name to look up.
          */
-        public static CharSequence getAppLabel(PackageManager pm, String packageName) {
+        public static CharSequence getAppLabel(Context context, UserHandle userHandle,
+                String packageName, FeatureFlags featureFlags) {
             try {
-                ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
-                CharSequence result = pm.getApplicationLabel(info);
-                Log.i(LOG_TAG, "package %s: name is %s", packageName, result);
-                return result;
+                if (featureFlags.telecomAppLabelProxyHsumAware()){
+                    Context userContext = context.createContextAsUser(userHandle, 0 /* flags */);
+                    PackageManager userPackageManager = userContext.getPackageManager();
+                    if (userPackageManager == null) {
+                        Log.w(LOG_TAG, "Could not determine app label since PackageManager is "
+                                + "null. Package name is %s", packageName);
+                        return null;
+                    }
+                    ApplicationInfo info = userPackageManager.getApplicationInfo(packageName, 0);
+                    CharSequence result = userPackageManager.getApplicationLabel(info);
+                    Log.i(LOG_TAG, "package %s: name is %s for user = %s", packageName, result,
+                            userHandle.toString());
+                    return result;
+                } else {
+                    // Legacy code path:
+                    PackageManager pm = context.getPackageManager();
+                    ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
+                    CharSequence result = pm.getApplicationLabel(info);
+                    Log.i(LOG_TAG, "package %s: name is %s", packageName, result);
+                    return result;
+                }
             } catch (PackageManager.NameNotFoundException nnfe) {
                 Log.w(LOG_TAG, "Could not determine app label. Package name is %s", packageName);
             }
@@ -47,5 +69,5 @@
         }
     }
 
-    CharSequence getAppLabel(String packageName);
+    CharSequence getAppLabel(String packageName, UserHandle userHandle);
 }
diff --git a/src/com/android/server/telecom/AudioRoute.java b/src/com/android/server/telecom/AudioRoute.java
index d3ed77d..aaded77 100644
--- a/src/com/android/server/telecom/AudioRoute.java
+++ b/src/com/android/server/telecom/AudioRoute.java
@@ -24,6 +24,7 @@
 
 import android.annotation.IntDef;
 import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
 import android.bluetooth.BluetoothStatusCodes;
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
@@ -300,7 +301,8 @@
                         pendingAudioRoute.setCommunicationDeviceType(mAudioRouteType);
                     }
                     Log.i(this, "onDestRouteAsPendingRoute: route=%s, "
-                            + "AudioManager#setCommunicationDevice()=%b", this, result);
+                            + "AudioManager#setCommunicationDevice(%s)=%b", this,
+                            audioDeviceTypeToString(mInfo.getType()), result);
                     break;
                 }
             }
@@ -314,13 +316,19 @@
         }
     }
 
-    // Takes care of cleaning up original audio route (i.e. clearCommunicationDevice,
-    // sending SPEAKER_OFF, or disconnecting SCO).
-    void onOrigRouteAsPendingRoute(boolean active, PendingAudioRoute pendingAudioRoute,
+    /**
+     * Takes care of cleaning up original audio route (i.e. clearCommunicationDevice,
+     * sending SPEAKER_OFF, or disconnecting SCO).
+     * @param wasActive Was the origin route active or not.
+     * @param pendingAudioRoute The pending audio route change we're performing.
+     * @param audioManager Good 'ol audio manager.
+     * @param bluetoothRouteManager The BT route manager.
+     */
+    void onOrigRouteAsPendingRoute(boolean wasActive, PendingAudioRoute pendingAudioRoute,
             AudioManager audioManager, BluetoothRouteManager bluetoothRouteManager) {
-        Log.i(this, "onOrigRouteAsPendingRoute: active (%b), type (%s)", active,
-                DEVICE_TYPE_STRINGS.get(mAudioRouteType));
-        if (active) {
+        Log.i(this, "onOrigRouteAsPendingRoute: wasActive (%b), type (%s), pending(%s)", wasActive,
+                DEVICE_TYPE_STRINGS.get(mAudioRouteType), pendingAudioRoute);
+        if (wasActive) {
             int result = clearCommunicationDevice(pendingAudioRoute, bluetoothRouteManager,
                     audioManager);
             if (mAudioRouteType == TYPE_SPEAKER) {
@@ -389,6 +397,20 @@
         return success;
     }
 
+    /**
+     * Clears the communication device; this takes into account the fact that SCO devices require
+     * us to call {@link BluetoothHeadset#disconnectAudio()} rather than
+     * {@link AudioManager#clearCommunicationDevice()}.
+     * As a general rule, if we are transitioning from an active route to another active route, we
+     * do NOT need to call {@link AudioManager#clearCommunicationDevice()}, but if the device is a
+     * legacy SCO device we WILL need to call {@link BluetoothHeadset#disconnectAudio()}.  We rely
+     * on the {@link PendingAudioRoute#isActive()} indicator to tell us if the destination route
+     * is going to be active or not.
+     * @param pendingAudioRoute The pending audio route transition we're implementing.
+     * @param bluetoothRouteManager The BT route manager.
+     * @param audioManager The audio manager.
+     * @return -1 if nothing was done, or the result code from the BT SCO disconnect.
+     */
     int clearCommunicationDevice(PendingAudioRoute pendingAudioRoute,
             BluetoothRouteManager bluetoothRouteManager, AudioManager audioManager) {
         // Try to see if there's a previously set device for communication that should be cleared.
@@ -402,9 +424,17 @@
             Log.i(this, "clearCommunicationDevice: Disconnecting SCO device.");
             result = bluetoothRouteManager.getDeviceManager().disconnectSco();
         } else {
-            Log.i(this, "clearCommunicationDevice: AudioManager#clearCommunicationDevice, type=%s",
-                    DEVICE_TYPE_STRINGS.get(pendingAudioRoute.getCommunicationDeviceType()));
-            audioManager.clearCommunicationDevice();
+            // Only clear communication device if the destination route will be inactive; route to
+            // route transitions do not require clearing the communication device.
+            boolean onlyClearCommunicationDeviceOnInactive =
+                    pendingAudioRoute.getFeatureFlags().onlyClearCommunicationDeviceOnInactive();
+            if (!onlyClearCommunicationDeviceOnInactive
+                    || (onlyClearCommunicationDeviceOnInactive && !pendingAudioRoute.isActive())) {
+                Log.i(this,
+                        "clearCommunicationDevice: AudioManager#clearCommunicationDevice, type=%s",
+                        DEVICE_TYPE_STRINGS.get(pendingAudioRoute.getCommunicationDeviceType()));
+                audioManager.clearCommunicationDevice();
+            }
         }
 
         if (result == BluetoothStatusCodes.SUCCESS) {
@@ -430,4 +460,23 @@
             pendingAudioRoute.clearPendingMessage(new Pair<>(SPEAKER_ON, null));
         }
     }
+
+    /**
+     * Get a human readable (for logs) version of an an audio device type.
+     * @param type the device type
+     * @return the human readable string
+     */
+    private static String audioDeviceTypeToString(int type) {
+        return switch (type) {
+            case AudioDeviceInfo.TYPE_BUILTIN_EARPIECE -> "earpiece";
+            case AudioDeviceInfo.TYPE_BUILTIN_SPEAKER -> "speaker";
+            case AudioDeviceInfo.TYPE_BUS -> "bus(auto speaker)";
+            case AudioDeviceInfo.TYPE_BLUETOOTH_SCO -> "bt sco";
+            case AudioDeviceInfo.TYPE_BLE_HEADSET -> "bt le";
+            case AudioDeviceInfo.TYPE_HEARING_AID -> "bt hearing aid";
+            case AudioDeviceInfo.TYPE_USB_HEADSET -> "usb headset";
+            case AudioDeviceInfo.TYPE_WIRED_HEADSET -> "wired headset";
+            default -> Integer.toString(type);
+        };
+    }
 }
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index df31e02..9e566e2 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -80,6 +80,7 @@
 import com.android.server.telecom.stats.CallFailureCause;
 import com.android.server.telecom.stats.CallStateChangedAtomWriter;
 import com.android.server.telecom.ui.ToastFactory;
+import com.android.server.telecom.callsequencing.CallTransaction;
 import com.android.server.telecom.callsequencing.TransactionManager;
 import com.android.server.telecom.callsequencing.VerifyCallStateChangeTransaction;
 import com.android.server.telecom.callsequencing.CallTransactionResult;
@@ -2832,20 +2833,20 @@
     }
 
     @VisibleForTesting
-    public void disconnect() {
-        disconnect(0);
+    public CompletableFuture<Boolean> disconnect() {
+        return disconnect(0);
     }
 
-    public void disconnect(String reason) {
-        disconnect(0, reason);
+    public CompletableFuture<Boolean> disconnect(String reason) {
+        return disconnect(0, reason);
     }
 
     /**
      * Attempts to disconnect the call through the connection service.
      */
     @VisibleForTesting
-    public void disconnect(long disconnectionTimeout) {
-        disconnect(disconnectionTimeout, "internal" /** reason */);
+    public CompletableFuture<Boolean> disconnect(long disconnectionTimeout) {
+        return disconnect(disconnectionTimeout, "internal" /* reason */);
     }
 
     /**
@@ -2855,16 +2856,24 @@
      *               as TelecomManager.
      */
     @VisibleForTesting
-    public void disconnect(long disconnectionTimeout, String reason) {
+    public CompletableFuture<Boolean> disconnect(long disconnectionTimeout,
+            String reason) {
         Log.addEvent(this, LogUtils.Events.REQUEST_DISCONNECT, reason);
 
         // Track that the call is now locally disconnecting.
         setLocallyDisconnecting(true);
         maybeSetCallAsDisconnectingChild();
 
+        CompletableFuture<Boolean> disconnectFutureHandler =
+                CompletableFuture.completedFuture(false);
         if (mState == CallState.NEW || mState == CallState.SELECT_PHONE_ACCOUNT ||
                 mState == CallState.CONNECTING) {
             Log.i(this, "disconnect: Aborting call %s", getId());
+            if (mFlags.enableCallSequencing()) {
+                disconnectFutureHandler = awaitCallStateChangeAndMaybeDisconnectCall(
+                        false /* shouldDisconnectUponTimeout */, "disconnect",
+                        CallState.DISCONNECTED, CallState.ABORTED);
+            }
             abort(disconnectionTimeout);
         } else if (mState != CallState.ABORTED && mState != CallState.DISCONNECTED) {
             if (mState == CallState.AUDIO_PROCESSING && !hasGoneActiveBefore()) {
@@ -2876,7 +2885,8 @@
                 setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.MISSED));
             }
             if (mTransactionalService != null) {
-                mTransactionalService.onDisconnect(this, getDisconnectCause());
+                disconnectFutureHandler = mTransactionalService.onDisconnect(this,
+                        getDisconnectCause());
                 Log.i(this, "Send Disconnect to transactional service for call");
             } else if (mConnectionService == null) {
                 Log.e(this, new Exception(), "disconnect() request on a call without a"
@@ -2887,9 +2897,15 @@
                 // confirms that the call was actually disconnected. Only then is the
                 // association between call and connection service severed, see
                 // {@link CallsManager#markCallAsDisconnected}.
+                if (mFlags.enableCallSequencing()) {
+                    disconnectFutureHandler = awaitCallStateChangeAndMaybeDisconnectCall(
+                            false /* shouldDisconnectUponTimeout */, "disconnect",
+                            CallState.DISCONNECTED);
+                }
                 mConnectionService.disconnect(this);
             }
         }
+        return disconnectFutureHandler;
     }
 
     void abort(long disconnectionTimeout) {
@@ -2932,29 +2948,35 @@
      * @param videoState The video state in which to answer the call.
      */
     @VisibleForTesting
-    public void answer(int videoState) {
+    public CompletableFuture<Boolean> answer(int videoState) {
+        CompletableFuture<Boolean> answerCallFuture = CompletableFuture.completedFuture(false);
         // Check to verify that the call is still in the ringing state. A call can change states
         // between the time the user hits 'answer' and Telecom receives the command.
         if (isRinging("answer")) {
+            Log.addEvent(this, LogUtils.Events.REQUEST_ACCEPT);
             if (!isVideoCallingSupportedByPhoneAccount() && VideoProfile.isVideo(videoState)) {
                 // Video calling is not supported, yet the InCallService is attempting to answer as
                 // video.  We will simply answer as audio-only.
                 videoState = VideoProfile.STATE_AUDIO_ONLY;
             }
             // At this point, we are asking the connection service to answer but we don't assume
-            // that it will work. Instead, we wait until confirmation from the connectino service
+            // that it will work. Instead, we wait until confirmation from the connection service
             // that the call is in a non-STATE_RINGING state before changing the UI. See
             // {@link ConnectionServiceAdapter#setActive} and other set* methods.
             if (mConnectionService != null) {
+                if (mFlags.enableCallSequencing()) {
+                    answerCallFuture = awaitCallStateChangeAndMaybeDisconnectCall(
+                            false /* shouldDisconnectUponTimeout */, "answer", CallState.ACTIVE);
+                }
                 mConnectionService.answer(this, videoState);
             } else if (mTransactionalService != null) {
-                mTransactionalService.onAnswer(this, videoState);
+                return mTransactionalService.onAnswer(this, videoState);
             } else {
                 Log.e(this, new NullPointerException(),
                         "answer call failed due to null CS callId=%s", getId());
             }
-            Log.addEvent(this, LogUtils.Events.REQUEST_ACCEPT);
         }
+        return answerCallFuture;
     }
 
     /**
@@ -3034,74 +3056,101 @@
      *               if the reject is initiated from an API such as TelecomManager.
      */
     @VisibleForTesting
-    public void reject(boolean rejectWithMessage, String textMessage, String reason) {
+    public CompletableFuture<Boolean> reject(boolean rejectWithMessage,
+            String textMessage, String reason) {
+        CompletableFuture<Boolean> rejectFutureHandler = CompletableFuture.completedFuture(false);
         if (mState == CallState.SIMULATED_RINGING) {
+            Log.addEvent(this, LogUtils.Events.REQUEST_REJECT, reason);
             // This handles the case where the user manually rejects a call that's in simulated
             // ringing. Since the call is already active on the connectionservice side, we want to
             // hangup, not reject.
             setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.REJECTED));
             if (mTransactionalService != null) {
-                mTransactionalService.onDisconnect(this,
+                return mTransactionalService.onDisconnect(this,
                         new DisconnectCause(DisconnectCause.REJECTED));
             } else if (mConnectionService != null) {
+                if (mFlags.enableCallSequencing()) {
+                    rejectFutureHandler = awaitCallStateChangeAndMaybeDisconnectCall(
+                            false /* shouldDisconnectUponTimeout */, "reject",
+                            CallState.DISCONNECTED);
+                }
                 mConnectionService.disconnect(this);
+                return rejectFutureHandler;
             } else {
                 Log.e(this, new NullPointerException(),
                         "reject call failed due to null CS callId=%s", getId());
             }
-            Log.addEvent(this, LogUtils.Events.REQUEST_REJECT, reason);
         } else if (isRinging("reject") || isAnswered("reject")) {
+            Log.addEvent(this, LogUtils.Events.REQUEST_REJECT, reason);
             // Ensure video state history tracks video state at time of rejection.
             mVideoStateHistory |= mVideoState;
 
             if (mTransactionalService != null) {
-                mTransactionalService.onDisconnect(this,
+                return mTransactionalService.onDisconnect(this,
                         new DisconnectCause(DisconnectCause.REJECTED));
             } else if (mConnectionService != null) {
+                if (mFlags.enableCallSequencing()) {
+                    rejectFutureHandler = awaitCallStateChangeAndMaybeDisconnectCall(
+                            false /* shouldDisconnectUponTimeout */, "reject",
+                            CallState.DISCONNECTED);
+                }
                 mConnectionService.reject(this, rejectWithMessage, textMessage);
+                return rejectFutureHandler;
             } else {
                 Log.e(this, new NullPointerException(),
                         "reject call failed due to null CS callId=%s", getId());
             }
-            Log.addEvent(this, LogUtils.Events.REQUEST_REJECT, reason);
         }
+        return rejectFutureHandler;
     }
 
     /**
      * Reject this Telecom call with the user-indicated reason.
      * @param rejectReason The user-indicated reason fore rejecting the call.
      */
-    public void reject(@android.telecom.Call.RejectReason int rejectReason) {
+    public CompletableFuture<Boolean> reject(@android.telecom.Call.RejectReason int rejectReason) {
+        CompletableFuture<Boolean> rejectFutureHandler = CompletableFuture.completedFuture(false);
         if (mState == CallState.SIMULATED_RINGING) {
+            Log.addEvent(this, LogUtils.Events.REQUEST_REJECT);
             // This handles the case where the user manually rejects a call that's in simulated
             // ringing. Since the call is already active on the connectionservice side, we want to
             // hangup, not reject.
             // Since its simulated reason we can't pass along the reject reason.
             setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.REJECTED));
             if (mTransactionalService != null) {
-                mTransactionalService.onDisconnect(this,
+                return mTransactionalService.onDisconnect(this,
                         new DisconnectCause(DisconnectCause.REJECTED));
             } else if (mConnectionService != null) {
+                if (mFlags.enableCallSequencing()) {
+                    rejectFutureHandler = awaitCallStateChangeAndMaybeDisconnectCall(
+                            false /* shouldDisconnectUponTimeout */, "reject",
+                            CallState.DISCONNECTED);
+                }
                 mConnectionService.disconnect(this);
             } else {
                 Log.e(this, new NullPointerException(),
                         "reject call failed due to null CS callId=%s", getId());
             }
-            Log.addEvent(this, LogUtils.Events.REQUEST_REJECT);
         } else if (isRinging("reject") || isAnswered("reject")) {
+            Log.addEvent(this, LogUtils.Events.REQUEST_REJECT, rejectReason);
             // Ensure video state history tracks video state at time of rejection.
             mVideoStateHistory |= mVideoState;
             if (mTransactionalService != null) {
-                mTransactionalService.onDisconnect(this,
+                return mTransactionalService.onDisconnect(this,
                         new DisconnectCause(DisconnectCause.REJECTED));
             } else if (mConnectionService != null) {
+                if (mFlags.enableCallSequencing()) {
+                    rejectFutureHandler = awaitCallStateChangeAndMaybeDisconnectCall(
+                            false /* shouldDisconnectUponTimeout */, "reject",
+                            CallState.DISCONNECTED);
+                }
                 mConnectionService.rejectWithReason(this, rejectReason);
             } else {
                 Log.e(this, new NullPointerException(),
                         "reject call failed due to null CS callId=%s", getId());
             }
-            Log.addEvent(this, LogUtils.Events.REQUEST_REJECT, rejectReason);
         }
+        return rejectFutureHandler;
     }
 
     /**
@@ -3151,41 +3200,46 @@
      * Puts the call on hold if it is currently active.
      */
     @VisibleForTesting
-    public void hold() {
-        hold(null /* reason */);
+    public CompletableFuture<Boolean> hold() {
+        return hold(null /* reason */);
     }
 
     /**
      * This method requests the ConnectionService or TransactionalService hosting the call to put
      * the call on hold
      */
-    public void hold(String reason) {
+    public CompletableFuture<Boolean> hold(String reason) {
+        CompletableFuture<Boolean> holdFutureHandler = CompletableFuture.completedFuture(false);
         if (mState == CallState.ACTIVE) {
+            Log.addEvent(this, LogUtils.Events.REQUEST_HOLD, reason);
             if (mTransactionalService != null) {
-                mTransactionalService.onSetInactive(this);
+                return mTransactionalService.onSetInactive(this);
             } else if (mConnectionService != null) {
-                if (mFlags.transactionalCsVerifier()) {
-                    awaitCallStateChangeAndMaybeDisconnectCall(CallState.ON_HOLD, isSelfManaged(),
-                            "hold");
+                if (mFlags.transactionalCsVerifier() || mFlags.enableCallSequencing()) {
+                    holdFutureHandler = awaitCallStateChangeAndMaybeDisconnectCall(isSelfManaged(),
+                            "hold", CallState.ON_HOLD);
                 }
                 mConnectionService.hold(this);
+                return holdFutureHandler;
             } else {
                 Log.e(this, new NullPointerException(),
                         "hold call failed due to null CS callId=%s", getId());
             }
-            Log.addEvent(this, LogUtils.Events.REQUEST_HOLD, reason);
         }
+        return holdFutureHandler;
     }
 
     /**
      * helper that can be used for any callback that requests a call state change and wants to
      * verify the change
      */
-    public void awaitCallStateChangeAndMaybeDisconnectCall(int targetCallState,
-            boolean shouldDisconnectUponTimeout, String callingMethod) {
+    public CompletableFuture<Boolean> awaitCallStateChangeAndMaybeDisconnectCall(
+            boolean shouldDisconnectUponTimeout, String callingMethod, int... targetCallStates) {
         TransactionManager tm = TransactionManager.getInstance();
-        tm.addTransaction(new VerifyCallStateChangeTransaction(mCallsManager.getLock(),
-                this, targetCallState), new OutcomeReceiver<>() {
+        CallTransaction callTransaction = new VerifyCallStateChangeTransaction(
+                mCallsManager.getLock(), this, targetCallStates);
+        return tm.addTransaction(callTransaction,
+                new OutcomeReceiver<>() {
             @Override
             public void onResult(CallTransactionResult result) {
                 Log.i(this, "awaitCallStateChangeAndMaybeDisconnectCall: %s: onResult:"
@@ -3210,22 +3264,29 @@
      * Releases the call from hold if it is currently active.
      */
     @VisibleForTesting
-    public void unhold() {
-        unhold(null /* reason */);
+    public CompletableFuture<Boolean> unhold() {
+        return unhold(null /* reason */);
     }
 
-    public void unhold(String reason) {
+    public CompletableFuture<Boolean> unhold(String reason) {
+        CompletableFuture<Boolean> unholdFutureHandler = CompletableFuture.completedFuture(false);
         if (mState == CallState.ON_HOLD) {
+            Log.addEvent(this, LogUtils.Events.REQUEST_UNHOLD, reason);
             if (mTransactionalService != null){
-                mTransactionalService.onSetActive(this);
+                return mTransactionalService.onSetActive(this);
             } else if (mConnectionService != null){
+                if (mFlags.enableCallSequencing()) {
+                    unholdFutureHandler = awaitCallStateChangeAndMaybeDisconnectCall(
+                            false /* shouldDisconnectUponTimeout */, "unhold", CallState.ACTIVE);
+                }
                 mConnectionService.unhold(this);
+                return unholdFutureHandler;
             } else {
                 Log.e(this, new NullPointerException(),
                         "unhold call failed due to null CS callId=%s", getId());
             }
-            Log.addEvent(this, LogUtils.Events.REQUEST_UNHOLD, reason);
         }
+        return unholdFutureHandler;
     }
 
     /** Checks if this is a live call or not. */
diff --git a/src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java b/src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java
index 8d5f9fd..84f7d8f 100644
--- a/src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java
+++ b/src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java
@@ -251,6 +251,7 @@
         }
 
         // Clear device and reset locally saved device type.
+        Log.i(this, "clearCommunicationDevice: AudioManager#clearCommunicationDevice()");
         mAudioManager.clearCommunicationDevice();
         mAudioDeviceType = sAUDIO_DEVICE_TYPE_INVALID;
 
diff --git a/src/com/android/server/telecom/CallAudioModeStateMachine.java b/src/com/android/server/telecom/CallAudioModeStateMachine.java
index e149bdd..d1fd564 100644
--- a/src/com/android/server/telecom/CallAudioModeStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioModeStateMachine.java
@@ -334,9 +334,15 @@
                     mAudioManager.abandonAudioFocusForCall();
                     // Clear requested communication device after the call ends.
                     if (mFeatureFlags.clearCommunicationDeviceAfterAudioOpsComplete()) {
-                        mCommunicationDeviceTracker.clearCommunicationDevice(
-                                mCommunicationDeviceTracker
-                                        .getCurrentLocallyRequestedCommunicationDevice());
+                        // Oh flags!  If we're using the refactored audio route switching, we should
+                        // not be using the communication device tracker; that is exclusively for
+                        // the old code path.
+                        if (!mFeatureFlags.dontUseCommunicationDeviceTracker()
+                                || !mFeatureFlags.useRefactoredAudioRouteSwitching()) {
+                            mCommunicationDeviceTracker.clearCommunicationDevice(
+                                    mCommunicationDeviceTracker
+                                            .getCurrentLocallyRequestedCommunicationDevice());
+                        }
                     }
                     return HANDLED;
                 default:
diff --git a/src/com/android/server/telecom/CallAudioRouteController.java b/src/com/android/server/telecom/CallAudioRouteController.java
index cf88cab..495f872 100644
--- a/src/com/android/server/telecom/CallAudioRouteController.java
+++ b/src/com/android/server/telecom/CallAudioRouteController.java
@@ -570,7 +570,8 @@
             }
             // override pending route while keep waiting for still pending messages for the
             // previous pending route
-            mPendingAudioRoute.setOrigRoute(mIsActive, mPendingAudioRoute.getDestRoute());
+            mPendingAudioRoute.setOrigRoute(mIsActive /* origin */,
+                    mPendingAudioRoute.getDestRoute(), active /* dest */);
         } else {
             if (mCurrentRoute.equals(destRoute) && (mIsActive == active)) {
                 return;
@@ -579,10 +580,12 @@
                     mIsActive, destRoute, active);
             // route to pending route
             if (getCallSupportedRoutes().contains(mCurrentRoute)) {
-                mPendingAudioRoute.setOrigRoute(mIsActive, mCurrentRoute);
+                mPendingAudioRoute.setOrigRoute(mIsActive /* origin */, mCurrentRoute,
+                        active /* dest */);
             } else {
                 // Avoid waiting for pending messages for an unavailable route
-                mPendingAudioRoute.setOrigRoute(mIsActive, DUMMY_ROUTE);
+                mPendingAudioRoute.setOrigRoute(mIsActive /* origin */, DUMMY_ROUTE,
+                        active /* dest */);
             }
             mIsPending = true;
         }
@@ -803,7 +806,6 @@
         if (bluetoothRoute != null) {
             Log.i(this, "request to route to bluetooth route: %s (active=%b)", bluetoothRoute,
                     mIsActive);
-            updateActiveBluetoothDevice(new Pair<>(type, deviceAddress));
             routeTo(mIsActive, bluetoothRoute);
         } else {
             Log.i(this, "request to route to unavailable bluetooth route - type (%s), address (%s)",
@@ -847,10 +849,6 @@
             // Fallback to an available route excluding the previously active device.
             routeTo(mIsActive, getBaseRoute(true, previouslyActiveDeviceAddress));
         }
-        // Clear out the active device for the BT audio type.
-        if (mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()) {
-            updateActiveBluetoothDevice(new Pair(type, null));
-        }
     }
 
     private void handleMuteChanged(boolean mute) {
@@ -986,12 +984,16 @@
      * @return {@link AudioRoute} of the BT device.
      */
     private AudioRoute getArbitraryBluetoothDevice() {
-        if (mActiveBluetoothDevice != null) {
-            return getBluetoothRoute(mActiveBluetoothDevice.first, mActiveBluetoothDevice.second);
-        } else if (!mBluetoothRoutes.isEmpty()) {
-            return mBluetoothRoutes.keySet().stream().toList().get(mBluetoothRoutes.size() - 1);
+        synchronized (mLock) {
+            if (mActiveBluetoothDevice != null) {
+                return getBluetoothRoute(
+                    mActiveBluetoothDevice.first, mActiveBluetoothDevice.second);
+            } else if (!mBluetoothRoutes.isEmpty()) {
+                return mBluetoothRoutes.keySet().stream().toList()
+                    .get(mBluetoothRoutes.size() - 1);
+            }
+            return null;
         }
-        return null;
     }
 
     private void handleSwitchHeadset() {
@@ -1026,15 +1028,28 @@
         // If SCO is once again connected or there's a pending message for BT_AUDIO_CONNECTED, then
         // we know that the device has reconnected or is in the middle of connecting. Ignore routing
         // out of this BT device.
-        if (mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue() && areExcludedBtAndDestBtSame
+        boolean isExcludedDeviceConnectingOrConnected = areExcludedBtAndDestBtSame
                 && (mIsScoAudioConnected || mPendingAudioRoute.getPendingMessages()
-                .contains(btDevicePendingMsg))) {
-            Log.i(this, "BT device with address (%s) is currently connecting/connected. "
-                    + "Ignore route switch.");
-        } else {
-            routeTo(mIsActive, calculateBaselineRoute(isExplicitUserRequest, includeBluetooth,
-                    btAddressToExclude));
+                .contains(btDevicePendingMsg));
+        // Check if the pending audio route or current route is already different from the route
+        // including the BT device that should be excluded from route selection.
+        boolean isCurrentOrDestRouteDifferent = btAddressToExclude != null
+                && ((mIsPending && !btAddressToExclude.equals(mPendingAudioRoute.getDestRoute()
+                .getBluetoothAddress())) || (!mIsPending && !btAddressToExclude.equals(
+                        mCurrentRoute.getBluetoothAddress())));
+        if (mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()) {
+            if (isExcludedDeviceConnectingOrConnected) {
+                Log.i(this, "BT device with address (%s) is currently connecting/connected. "
+                        + "Ignoring route switch.", btAddressToExclude);
+                return;
+            } else if (isCurrentOrDestRouteDifferent) {
+                Log.i(this, "Current or pending audio route isn't routed to device with address "
+                        + "(%s). Ignoring route switch.", btAddressToExclude);
+                return;
+            }
         }
+        routeTo(mIsActive, calculateBaselineRoute(isExplicitUserRequest, includeBluetooth,
+                btAddressToExclude));
     }
 
     private void handleSpeakerOn() {
@@ -1045,7 +1060,8 @@
             mStatusBarNotifier.notifySpeakerphone(mCallsManager.hasAnyCalls());
         } else {
             if (mSpeakerDockRoute != null && getCallSupportedRoutes().contains(mSpeakerDockRoute)
-                    && mSpeakerDockRoute.getType() == AudioRoute.TYPE_SPEAKER) {
+                    && mSpeakerDockRoute.getType() == AudioRoute.TYPE_SPEAKER
+                    && mCurrentRoute.getType() != AudioRoute.TYPE_SPEAKER) {
                 routeTo(mIsActive, mSpeakerDockRoute);
                 // Since the route switching triggered by this message, we need to manually send it
                 // again so that we won't stuck in the pending route
@@ -1444,8 +1460,14 @@
                 continue;
             }
             // Check if the most recently active device is a watch device.
-            if (i == (bluetoothRoutes.size() - 1) && device.equals(mCallAudioState
-                    .getActiveBluetoothDevice()) && mBluetoothRouteManager.isWatch(device)) {
+            boolean isActiveDevice;
+            synchronized (mLock) {
+                isActiveDevice = mActiveBluetoothDevice != null
+                    && device.getAddress().equals(mActiveBluetoothDevice.second);
+            }
+            if (i == (bluetoothRoutes.size() - 1) && mBluetoothRouteManager.isWatch(device)
+                    && (device.equals(mCallAudioState.getActiveBluetoothDevice())
+                    || isActiveDevice)) {
                 Log.i(this, "getActiveWatchOrNonWatchDeviceRoute: Routing to active watch - %s",
                         bluetoothRoutes.get(0));
                 return bluetoothRoutes.get(0);
@@ -1556,29 +1578,32 @@
      *                           address of the device.
      */
     public void updateActiveBluetoothDevice(Pair<Integer, String> device) {
-        mActiveDeviceCache.put(device.first, device.second);
-        // Update most recently active device if address isn't null (meaning some device is active).
-        if (device.second != null) {
-            mActiveBluetoothDevice = device;
-        } else {
-            // If a device was removed, check to ensure that no other device is still considered
-            // active.
-            boolean hasActiveDevice = false;
-            List<Map.Entry<Integer, String>> activeBtDevices = new ArrayList<>(
-                    mActiveDeviceCache.entrySet());
-            for (Map.Entry<Integer,String> activeDevice : activeBtDevices) {
-                Integer btAudioType = activeDevice.getKey();
-                String address = activeDevice.getValue();
-                if (address != null) {
-                    hasActiveDevice = true;
-                    if (mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()) {
-                        mActiveBluetoothDevice = new Pair<>(btAudioType, address);
+        synchronized (mLock) {
+            mActiveDeviceCache.put(device.first, device.second);
+            // Update most recently active device if address isn't null (meaning
+            // some device is active).
+            if (device.second != null) {
+                mActiveBluetoothDevice = device;
+            } else {
+                // If a device was removed, check to ensure that no other device is
+                //still considered active.
+                boolean hasActiveDevice = false;
+                List<Map.Entry<Integer, String>> activeBtDevices =
+                        new ArrayList<>(mActiveDeviceCache.entrySet());
+                for (Map.Entry<Integer, String> activeDevice : activeBtDevices) {
+                    Integer btAudioType = activeDevice.getKey();
+                    String address = activeDevice.getValue();
+                    if (address != null) {
+                        hasActiveDevice = true;
+                        if (mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()) {
+                            mActiveBluetoothDevice = new Pair<>(btAudioType, address);
+                        }
+                        break;
                     }
-                    break;
                 }
-            }
-            if (!hasActiveDevice) {
-                mActiveBluetoothDevice = null;
+                if (!hasActiveDevice) {
+                    mActiveBluetoothDevice = null;
+                }
             }
         }
     }
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 540c152..712c6a9 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -27,6 +27,8 @@
 import static android.provider.CallLog.Calls.USER_MISSED_NOT_RUNNING;
 import static android.provider.CallLog.Calls.USER_MISSED_NO_ANSWER;
 import static android.provider.CallLog.Calls.USER_MISSED_SHORT_RING;
+import static android.telecom.CallAttributes.DIRECTION_INCOMING;
+import static android.telecom.CallAttributes.DIRECTION_OUTGOING;
 import static android.telecom.TelecomManager.ACTION_POST_CALL;
 import static android.telecom.TelecomManager.DURATION_LONG;
 import static android.telecom.TelecomManager.DURATION_MEDIUM;
@@ -132,6 +134,8 @@
 import com.android.server.telecom.callfiltering.IncomingCallFilterGraphProvider;
 import com.android.server.telecom.callredirection.CallRedirectionProcessor;
 import com.android.server.telecom.callsequencing.CallSequencingController;
+import com.android.server.telecom.callsequencing.CallTransaction;
+import com.android.server.telecom.callsequencing.voip.IncomingCallTransaction;
 import com.android.server.telecom.components.ErrorDialogActivity;
 import com.android.server.telecom.components.TelecomBroadcastReceiver;
 import com.android.server.telecom.callsequencing.CallsManagerCallSequencingAdapter;
@@ -324,7 +328,7 @@
     public static final String TELEPHONY_HAS_DEFAULT_BUT_TELECOM_DOES_NOT_MSG =
             "Telephony has a default MO acct but Telecom prompted user for MO";
 
-    private static final int[] OUTGOING_CALL_STATES =
+    public static final int[] OUTGOING_CALL_STATES =
             {CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING,
                     CallState.PULLING};
 
@@ -703,7 +707,7 @@
                 ringtoneFactory, systemVibrator,
                 new Ringer.VibrationEffectProxy(), mInCallController,
                 mContext.getSystemService(NotificationManager.class),
-                accessibilityManagerAdapter, featureFlags);
+                accessibilityManagerAdapter, featureFlags, mAnomalyReporter);
         if (featureFlags.telecomResolveHiddenDependencies()) {
             // This is now deprecated
             mCallRecordingTonePlayer = null;
@@ -744,8 +748,8 @@
                 ? mContext.getSystemService(BlockedNumbersManager.class)
                 : null;
         mCallSequencingAdapter = new CallsManagerCallSequencingAdapter(this,
-                new CallSequencingController(this, mFeatureFlags.enableCallSequencing()),
-                mFeatureFlags.enableCallSequencing());
+                new CallSequencingController(this, mContext,
+                        mFeatureFlags), mFeatureFlags);
 
         if (mFeatureFlags.useImprovedListenerOrder()) {
             mListeners.add(mInCallController);
@@ -939,8 +943,8 @@
         String defaultDialerPackageName = telecomManager.getDefaultDialerPackage(userHandle);
         String userChosenPackageName = getRoleManagerAdapter().
                 getDefaultCallScreeningApp(userHandle);
-        AppLabelProxy appLabelProxy = packageName -> AppLabelProxy.Util.getAppLabel(
-                mContext.getPackageManager(), packageName);
+        AppLabelProxy appLabelProxy = (packageName, user) -> AppLabelProxy.Util.getAppLabel(
+                mContext, user, packageName, mFeatureFlags);
         ParcelableCallUtils.Converter converter = new ParcelableCallUtils.Converter();
 
         IncomingCallFilterGraph graph = mIncomingCallFilterGraphProvider.createGraph(incomingCall,
@@ -1874,6 +1878,37 @@
                 originalIntent, callingPackage, false);
     }
 
+    /**
+     * Creates a transaction representing either the outgoing or incoming transactional call.
+     * @param callId The call id associated with the call.
+     * @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> createTransactionalCall(String callId,
+            CallAttributes callAttributes, Bundle extras, String callingPackage) {
+        CompletableFuture<CallTransaction> transaction;
+        // create transaction based on the call direction
+        switch (callAttributes.getDirection()) {
+            case DIRECTION_OUTGOING:
+                transaction = mCallSequencingAdapter.createTransactionalOutgoingCall(callId,
+                        callAttributes, extras, callingPackage);
+                break;
+            case DIRECTION_INCOMING:
+                transaction = CompletableFuture.completedFuture(new IncomingCallTransaction(
+                        callId, callAttributes, this, extras, mFeatureFlags));
+                break;
+            default:
+                throw new IllegalArgumentException(String.format("Invalid Call Direction. "
+                                + "Was [%d] but should be within [%d,%d]",
+                        callAttributes.getDirection(), DIRECTION_INCOMING,
+                        DIRECTION_OUTGOING));
+        }
+        return transaction;
+    }
+
     private String generateNextCallId(Bundle extras) {
         if (extras != null && extras.containsKey(TelecomManager.TRANSACTION_CALL_ID_KEY)) {
             return extras.getString(TelecomManager.TRANSACTION_CALL_ID_KEY);
@@ -2592,8 +2627,8 @@
                 theCall,
                 new AppLabelProxy() {
                     @Override
-                    public CharSequence getAppLabel(String packageName) {
-                        return Util.getAppLabel(mContext.getPackageManager(), packageName);
+                    public CharSequence getAppLabel(String packageName, UserHandle userHandle) {
+                        return Util.getAppLabel(mContext, userHandle, packageName, mFeatureFlags);
                     }
                 }).process();
         future.thenApply( v -> {
@@ -3214,7 +3249,7 @@
         }
 
         CharSequence requestingAppName = AppLabelProxy.Util.getAppLabel(
-                mContext.getPackageManager(), requestingPackageName);
+                mContext, call.getAssociatedUser(), requestingPackageName, mFeatureFlags);
         if (requestingAppName == null) {
             requestingAppName = requestingPackageName;
         }
@@ -3430,35 +3465,42 @@
             Log.w(this, "Unknown call (%s) asked to disconnect", call);
         } else {
             mLocallyDisconnectingCalls.add(call);
-            int previousState = call.getState();
-            call.disconnect();
-            for (CallsManagerListener listener : mListeners) {
-                listener.onCallStateChanged(call, previousState, call.getState());
-            }
-            // Cancel any of the outgoing call futures if they're still around.
-            if (mPendingCallConfirm != null && !mPendingCallConfirm.isDone()) {
-                mPendingCallConfirm.complete(null);
-                mPendingCallConfirm = null;
-            }
-            if (mPendingAccountSelection != null && !mPendingAccountSelection.isDone()) {
-                mPendingAccountSelection.complete(null);
-                mPendingAccountSelection = null;
-            }
+            mCallSequencingAdapter.disconnectCall(call);
         }
     }
 
     /**
-     * Instructs Telecom to disconnect all calls.
+     * Disconnects the provided call. This is only used when
+     * {@link FeatureFlags#enableCallSequencing()} is false.
+     * @param call The call to disconnect.
+     * @param previousState The previous call state before the call is disconnected.
      */
-    void disconnectAllCalls() {
-        Log.v(this, "disconnectAllCalls");
-
-        for (Call call : mCalls) {
-            disconnectCall(call);
-        }
+    public void disconnectCallOld(Call call, int previousState) {
+        call.disconnect();
+        processDisconnectCallAndCleanup(call, previousState);
     }
 
     /**
+     * Helper to process the call state change upon disconnecting the provided call and performs
+     * local cleanup to clear the outgoing call futures, if they exist.
+     * @param call The call to disconnect.
+     * @param previousState The previous call state before the call is disconnected.
+     */
+    public void processDisconnectCallAndCleanup(Call call, int previousState) {
+        for (CallsManagerListener listener : mListeners) {
+            listener.onCallStateChanged(call, previousState, call.getState());
+        }
+        // Cancel any of the outgoing call futures if they're still around.
+        if (mPendingCallConfirm != null && !mPendingCallConfirm.isDone()) {
+            mPendingCallConfirm.complete(null);
+            mPendingCallConfirm = null;
+        }
+        if (mPendingAccountSelection != null && !mPendingAccountSelection.isDone()) {
+            mPendingAccountSelection.complete(null);
+            mPendingAccountSelection = null;
+        }
+    }
+    /**
      * Disconnects calls for any other {@link PhoneAccountHandle} but the one specified.
      * Note: As a protective measure, will NEVER disconnect an emergency call.  Although that
      * situation should never arise, its a good safeguard.
@@ -3549,6 +3591,16 @@
                 new RequestCallback(new ActionUnHoldCall(call, activeCallId)));
     }
 
+    public void requestActionSetActiveCall(Call call, String tag) {
+        mConnectionSvrFocusMgr.requestFocus(call,
+                new RequestCallback(new ActionSetCallState(call, CallState.ACTIVE, tag)));
+    }
+
+    public void requestFocusActionAnswerCall(Call call, int videoState) {
+        mConnectionSvrFocusMgr.requestFocus(call, new CallsManager.RequestCallback(
+                new ActionAnswerCall(call, videoState)));
+    }
+
     @Override
     public void onExtrasRemoved(Call c, int source, List<String> keys) {
         if (source != Call.SOURCE_CONNECTION_SERVICE) {
@@ -3915,15 +3967,10 @@
         maybeMoveToSpeakerPhone(call);
     }
 
-    void requestFocusActionAnswerCall(Call call, int videoState) {
-        mConnectionSvrFocusMgr.requestFocus(call, new CallsManager.RequestCallback(
-                new CallsManager.ActionAnswerCall(call, videoState)));
-    }
-
     /**
      * Returns true if the active call is held.
      */
-    boolean holdActiveCallForNewCall(Call call) {
+    public boolean holdActiveCallForNewCall(Call call) {
         Call activeCall = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
         Log.i(this, "holdActiveCallForNewCall, newCall: %s, activeCall: %s", call.getId(),
                 (activeCall == null ? "<none>" : activeCall.getId()));
@@ -4009,23 +4056,8 @@
                 return;
             }
 
-            if (holdActiveCallForNewCall(newCall)) {
-                // Transactional clients do not call setHold but the request was sent to set the
-                // call as inactive and it has already been acked by this point.
-                markCallAsOnHold(activeCall);
-                callback.onResult(true);
-            } else {
-                // It's possible that holdActiveCallForNewCall disconnected the activeCall.
-                // Therefore, the activeCalls state should be checked before failing.
-                if (activeCall.isLocallyDisconnecting()) {
-                    callback.onResult(true);
-                } else {
-                    Log.i(this, mTag + "active call could not be held or disconnected");
-                    callback.onError(
-                            new CallException("activeCall could not be held or disconnected",
-                                    CallException.CODE_CANNOT_HOLD_CURRENT_ACTIVE_CALL));
-                }
-            }
+            mCallSequencingAdapter.transactionHoldPotentialActiveCallForNewCall(newCall,
+                    activeCall, callback);
         } else {
             // before attempting CallsManager#holdActiveCallForNewCall(Call), check if it'll fail
             // early
@@ -4053,6 +4085,28 @@
         }
     }
 
+    public void transactionHoldPotentialActiveCallForNewCallOld(Call newCall,
+            Call activeCall, OutcomeReceiver<Boolean, CallException> callback) {
+        if (holdActiveCallForNewCall(newCall)) {
+            // Transactional clients do not call setHold but the request was sent to set the
+            // call as inactive and it has already been acked by this point.
+            markCallAsOnHold(activeCall);
+            callback.onResult(true);
+        } else {
+            // It's possible that holdActiveCallForNewCall disconnected the activeCall.
+            // Therefore, the activeCalls state should be checked before failing.
+            if (activeCall.isLocallyDisconnecting()) {
+                callback.onResult(true);
+            } else {
+                Log.i(this, "transactionHoldPotentialActiveCallForNewCallOld: active call could "
+                        + "not be held or disconnected");
+                callback.onError(
+                        new CallException("activeCall could not be held or disconnected",
+                                CallException.CODE_CANNOT_HOLD_CURRENT_ACTIVE_CALL));
+            }
+        }
+    }
+
     private boolean canHoldOrSwapActiveCall(Call activeCall, Call newCall) {
         return canHold(activeCall) || sameSourceHoldCase(activeCall, newCall);
     }
@@ -4064,8 +4118,6 @@
     /**
      * CS: Mark a call as active. If the call is self-mangaed, we will also hold any active call
      * before moving the self-managed call to active.
-     * <p>
-     * Note: Only used when {@link FeatureFlags#enableCallSequencing()} is false.
      */
     @VisibleForTesting
     public void markCallAsActive(Call call) {
@@ -4075,13 +4127,7 @@
             // to active directly. We should hold or disconnect the current active call based on the
             // holdability, and request the call focus for the self-managed call before the state
             // change.
-            holdActiveCallForNewCall(call);
-            mConnectionSvrFocusMgr.requestFocus(
-                    call,
-                    new RequestCallback(new ActionSetCallState(
-                            call,
-                            CallState.ACTIVE,
-                            "active set explicitly for self-managed")));
+            mCallSequencingAdapter.markCallAsActiveSelfManagedCall(call);
         } else {
             if (mPendingAudioProcessingCall == call) {
                 if (mCalls.contains(call)) {
@@ -4103,8 +4149,6 @@
 
     /**
      * Mark a call as on hold after the hold operation has already completed.
-     * <p>
-     * Note: only used when {@link FeatureFlags#enableCallSequencing()} is false.
      */
     public void markCallAsOnHold(Call call) {
         setCallState(call, CallState.ON_HOLD, "on-hold set explicitly");
@@ -4283,17 +4327,19 @@
         removeCall(call);
         boolean isLocallyDisconnecting = mLocallyDisconnectingCalls.contains(call);
         mLocallyDisconnectingCalls.remove(call);
-        mCallSequencingAdapter.unholdCallForRemoval(call, isLocallyDisconnecting);
+        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.
-     * <p>
-     * Note: This is only used when {@link FeatureFlags#enableCallSequencing()} is set to false.
+     *
+     * 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();
@@ -4309,7 +4355,7 @@
                     && foregroundCall.getState() == CallState.ON_HOLD
                     && areFromSameSource(foregroundCall, removedCall)) {
 
-                foregroundCall.unhold();
+                unholdForegroundCallFuture = foregroundCall.unhold();
             }
         } else if (foregroundCall != null &&
                 !foregroundCall.can(Connection.CAPABILITY_SUPPORT_HOLD) &&
@@ -4320,7 +4366,14 @@
             // has no means of unholding it themselves.
             Log.i(this, "maybeMoveHeldCallToForeground: Auto-unholding held foreground call (call "
                     + "doesn't support hold)");
-            foregroundCall.unhold();
+            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.");
         }
     }
 
@@ -4395,7 +4448,7 @@
         return getFirstCallWithState(CallState.RINGING, CallState.ANSWERED) != null;
     }
 
-    boolean hasRingingOrSimulatedRingingCall() {
+    public boolean hasRingingOrSimulatedRingingCall() {
         return getFirstCallWithState(
                 CallState.SIMULATED_RINGING, CallState.RINGING, CallState.ANSWERED) != null;
     }
@@ -5147,7 +5200,7 @@
                 exceptCall, phoneAccountHandle, ANY_CALL_STATE);
     }
 
-    private boolean hasMaximumManagedHoldingCalls(Call exceptCall) {
+    public boolean hasMaximumManagedHoldingCalls(Call exceptCall) {
         return MAXIMUM_HOLD_CALLS <= getNumCallsWithState(false /* isSelfManaged */, exceptCall,
                 null /* phoneAccountHandle */, CallState.ON_HOLD);
     }
@@ -5163,7 +5216,7 @@
                 phoneAccountHandle, CallState.RINGING, CallState.ANSWERED);
     }
 
-    private boolean hasMaximumOutgoingCalls(Call exceptCall) {
+    public boolean hasMaximumOutgoingCalls(Call exceptCall) {
         return MAXIMUM_LIVE_CALLS <= getNumCallsWithState(CALL_FILTER_ALL,
                 exceptCall, null /* phoneAccountHandle */, OUTGOING_CALL_STATES);
     }
@@ -5280,7 +5333,7 @@
      * <p>
      * Note: This method is only applicable when {@link FeatureFlags#enableCallSequencing()}
      * is false.
-     * @param call The new pending outgoing call.
+     * @param emergencyCall The new pending outgoing call.
      * @return true if room was made, false if no room could be made.
      */
     @VisibleForTesting
@@ -5475,41 +5528,12 @@
             return 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);
-            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;
+        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);
         }
 
         // TODO: Remove once b/23035408 has been corrected.
@@ -5575,12 +5599,70 @@
     }
 
     /**
+     * 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.
      * @return The first non-null phone account handle of the children, or {@code null} if none.
      */
-    private PhoneAccountHandle getFirstChildPhoneAccount(Call parentCall) {
+    public PhoneAccountHandle getFirstChildPhoneAccount(Call parentCall) {
         for (Call childCall : parentCall.getChildCalls()) {
             PhoneAccountHandle childPhoneAccount = childCall.getTargetPhoneAccount();
             if (childPhoneAccount != null) {
@@ -6662,7 +6744,15 @@
         public void performAction() {
             synchronized (mLock) {
                 Log.d(this, "perform unhold call for %s", mCall);
-                mCall.unhold("held " + mPreviouslyHeldCallId);
+                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.");
+                }
             }
         }
     }
@@ -6684,10 +6774,11 @@
                     listener.onIncomingCallAnswered(mCall);
                 }
 
+                CompletableFuture<Boolean> answerCallFuture = null;
                 // We do not update the UI until we get confirmation of the answer() through
                 // {@link #markCallAsActive}.
                 if (mCall.getState() == CallState.RINGING) {
-                    mCall.answer(mVideoState);
+                    answerCallFuture = mCall.answer(mVideoState);
                     setCallState(mCall, CallState.ANSWERED, "answered");
                 } else if (mCall.getState() == CallState.SIMULATED_RINGING) {
                     // If the call's in simulated ringing, we don't have to wait for the CS --
@@ -6698,12 +6789,19 @@
                     // In certain circumstances, the connection service can lose track of a request
                     // to answer a call. Therefore, if the user presses answer again, still send it
                     // on down, but log a warning in the process and don't change the call state.
-                    mCall.answer(mVideoState);
+                    answerCallFuture = mCall.answer(mVideoState);
                     Log.w(this, "Duplicate answer request for call %s", mCall.getId());
                 }
                 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.");
+                }
             }
         }
     }
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index 14c8f62..260c238 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -130,11 +130,7 @@
                 synchronized (mLock) {
                     logIncoming("handleCreateConnectionComplete %s", callId);
                     Call call = mCallIdMapper.getCall(callId);
-                    if (call != null && mScheduledFutureMap.containsKey(call)) {
-                        ScheduledFuture<?> existingTimeout = mScheduledFutureMap.get(call);
-                        existingTimeout.cancel(false /* cancelIfRunning */);
-                        mScheduledFutureMap.remove(call);
-                    }
+                    maybeRemoveCleanupFuture(call);
                     // Check status hints image for cross user access
                     if (connection.getStatusHints() != null) {
                         Icon icon = connection.getStatusHints().getIcon();
@@ -174,11 +170,7 @@
                 synchronized (mLock) {
                     logIncoming("handleCreateConferenceComplete %s", callId);
                     Call call = mCallIdMapper.getCall(callId);
-                    if (call != null && mScheduledFutureMap.containsKey(call)) {
-                        ScheduledFuture<?> existingTimeout = mScheduledFutureMap.get(call);
-                        existingTimeout.cancel(false /* cancelIfRunning */);
-                        mScheduledFutureMap.remove(call);
-                    }
+                    maybeRemoveCleanupFuture(call);
                     // Check status hints image for cross user access
                     if (conference.getStatusHints() != null) {
                         Icon icon = conference.getStatusHints().getIcon();
@@ -1678,6 +1670,9 @@
                             Log.getExternalSession(TELECOM_ABBREVIATION));
                 } catch (RemoteException e) {
                     Log.e(this, e, "Failure to createConference -- %s", getComponentName());
+                    if (mFlags.dontTimeoutDestroyedCalls()) {
+                        maybeRemoveCleanupFuture(call);
+                    }
                     mPendingResponses.remove(callId).handleCreateConferenceFailure(
                             new DisconnectCause(DisconnectCause.ERROR, e.toString()));
                 }
@@ -1708,6 +1703,9 @@
                     Log.i(ConnectionServiceWrapper.this, "Call not present"
                             + " in call id mapper, maybe it was aborted before the bind"
                             + " completed successfully?");
+                    if (mFlags.dontTimeoutDestroyedCalls()) {
+                        maybeRemoveCleanupFuture(call);
+                    }
                     response.handleCreateConnectionFailure(
                             new DisconnectCause(DisconnectCause.CANCELED));
                     return;
@@ -1793,6 +1791,9 @@
                 mScheduledFutureMap.put(call, future);
                 try {
                     if (mFlags.cswServiceInterfaceIsNull() && mServiceInterface == null) {
+                        if (mFlags.dontTimeoutDestroyedCalls()) {
+                            maybeRemoveCleanupFuture(call);
+                        }
                         mPendingResponses.remove(callId).handleCreateConnectionFailure(
                                 new DisconnectCause(DisconnectCause.ERROR,
                                         "CSW#oCC ServiceInterface is null"));
@@ -1807,6 +1808,9 @@
                     }
                 } catch (RemoteException e) {
                     Log.e(this, e, "Failure to createConnection -- %s", getComponentName());
+                    if (mFlags.dontTimeoutDestroyedCalls()) {
+                        maybeRemoveCleanupFuture(call);
+                    }
                     mPendingResponses.remove(callId).handleCreateConnectionFailure(
                             new DisconnectCause(DisconnectCause.ERROR, e.toString()));
                 }
@@ -2286,6 +2290,9 @@
         if (response != null) {
             response.handleCreateConnectionFailure(disconnectCause);
         }
+        if (mFlags.dontTimeoutDestroyedCalls()) {
+            maybeRemoveCleanupFuture(mCallIdMapper.getCall(callId));
+        }
 
         mCallIdMapper.removeCall(callId);
     }
@@ -2295,6 +2302,9 @@
         if (response != null) {
             response.handleCreateConnectionFailure(disconnectCause);
         }
+        if (mFlags.dontTimeoutDestroyedCalls()) {
+            maybeRemoveCleanupFuture(call);
+        }
 
         mCallIdMapper.removeCall(call);
     }
@@ -2754,4 +2764,20 @@
     public void setAnomalyReporterAdapter(AnomalyReporterAdapter mAnomalyReporterAdapter){
         mAnomalyReporter = mAnomalyReporterAdapter;
     }
+
+    /**
+     * Given a call, unschedule and cancel the cleanup future.
+     * @param call the call.
+     */
+    private void maybeRemoveCleanupFuture(Call call) {
+        if (call == null) {
+            return;
+        }
+        ScheduledFuture<?> future = mScheduledFutureMap.remove(call);
+        if (future == null) {
+            return;
+        }
+        future.cancel(false /* interrupt */);
+
+    }
 }
diff --git a/src/com/android/server/telecom/PendingAudioRoute.java b/src/com/android/server/telecom/PendingAudioRoute.java
index d21ac56..dde1d8d 100644
--- a/src/com/android/server/telecom/PendingAudioRoute.java
+++ b/src/com/android/server/telecom/PendingAudioRoute.java
@@ -70,8 +70,23 @@
         mCommunicationDeviceType = AudioRoute.TYPE_INVALID;
     }
 
-    void setOrigRoute(boolean active, AudioRoute origRoute) {
-        origRoute.onOrigRouteAsPendingRoute(active, this, mAudioManager, mBluetoothRouteManager);
+    /**
+     * Sets the originating route information, and begins the process of transitioning OUT of the
+     * originating route.
+     * Note: We also pass in whether the destination route is going to be active.  This is so that
+     * {@link AudioRoute#onOrigRouteAsPendingRoute(boolean, PendingAudioRoute, AudioManager,
+     * BluetoothRouteManager)} knows whether or not the destination route will be active or not and
+     * can determine whether or not it needs to call {@link AudioManager#clearCommunicationDevice()}
+     * or not.  To optimize audio performance we only need to clear the communication device if the
+     * end result is going to be that we are in an inactive state.
+     * @param isOriginActive Whether the origin is active.
+     * @param origRoute The origin.
+     * @param isDestActive Whether the destination will be active.
+     */
+    void setOrigRoute(boolean isOriginActive, AudioRoute origRoute, boolean isDestActive) {
+        mActive = isDestActive;
+        origRoute.onOrigRouteAsPendingRoute(isOriginActive, this, mAudioManager,
+                mBluetoothRouteManager);
         mOrigRoute = origRoute;
     }
 
@@ -134,6 +149,10 @@
         return mPendingMessages;
     }
 
+    /**
+     * Whether the destination {@link #getDestRoute()} will be active or not.
+     * @return {@code true} if destination will be active, {@code false} otherwise.
+     */
     public boolean isActive() {
         return mActive;
     }
@@ -154,4 +173,14 @@
     public FeatureFlags getFeatureFlags() {
         return mFeatureFlags;
     }
+
+    @Override
+    public String toString() {
+        return "PendingAudioRoute{" +
+                ", mOrigRoute=" + mOrigRoute +
+                ", mDestRoute=" + mDestRoute +
+                ", mActive=" + mActive +
+                ", mCommunicationDeviceType=" + mCommunicationDeviceType +
+                '}';
+    }
 }
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index 796a62b..1a1af92 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -1326,7 +1326,10 @@
 
             // Ensure name is correct.
             CharSequence newLabel = mAppLabelProxy.getAppLabel(
-                    account.getAccountHandle().getComponentName().getPackageName());
+                    account.getAccountHandle().getComponentName().getPackageName(),
+                    UserUtil.getAssociatedUserForCall(
+                            mTelecomFeatureFlags.associatedUserRefactorForWorkProfile(),
+                            this, UserHandle.CURRENT, account.getAccountHandle()));
 
             account = account.toBuilder()
                     .setLabel(newLabel)
diff --git a/src/com/android/server/telecom/Ringer.java b/src/com/android/server/telecom/Ringer.java
index 12778b0..bfaadf0 100644
--- a/src/com/android/server/telecom/Ringer.java
+++ b/src/com/android/server/telecom/Ringer.java
@@ -59,6 +59,7 @@
 import java.io.InputStreamReader;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
+import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.ExecutionException;
@@ -176,6 +177,11 @@
 
     private static VolumeShaper.Configuration mVolumeShaperConfig;
 
+    public static final UUID GET_RINGER_MODE_ANOMALY_UUID =
+            UUID.fromString("eb10505b-4d7b-4fab-b4a1-a18186799065");
+    public static final String GET_RINGER_MODE_ANOMALY_MSG = "AM#GetRingerMode() and"
+            + " AM#GetRingerModeInternal() are returning diff values when DoNotDisturb is OFF!";
+
     /**
      * Used to keep ordering of unanswered incoming calls. There can easily exist multiple incoming
      * calls and explicit ordering is useful for maintaining the proper state of the ringer.
@@ -191,6 +197,8 @@
     private final boolean mIsHapticPlaybackSupportedByDevice;
     private final FeatureFlags mFlags;
     private final boolean mRingtoneVibrationSupported;
+    private final AnomalyReporterAdapter mAnomalyReporter;
+
     /**
      * For unit testing purposes only; when set, {@link #startRinging(Call, boolean)} will complete
      * the future provided by the test using {@link #setBlockOnRingingFuture(CompletableFuture)}.
@@ -237,7 +245,8 @@
             InCallController inCallController,
             NotificationManager notificationManager,
             AccessibilityManagerAdapter accessibilityManagerAdapter,
-            FeatureFlags featureFlags) {
+            FeatureFlags featureFlags,
+            AnomalyReporterAdapter anomalyReporter) {
 
         mLock = new Object();
         mSystemSettingsUtil = systemSettingsUtil;
@@ -252,6 +261,7 @@
         mVibrationEffectProxy = vibrationEffectProxy;
         mNotificationManager = notificationManager;
         mAccessibilityManagerAdapter = accessibilityManagerAdapter;
+        mAnomalyReporter = anomalyReporter;
 
         mDefaultVibrationEffect =
                 loadDefaultRingVibrationEffect(
@@ -405,10 +415,9 @@
                     // If ringer is not audible for this call, then the phone is in "Vibrate" mode.
                     // Use haptic-only ringtone or do not play anything.
                     isHapticOnly = true;
-                    if (DEBUG_RINGER) {
-                        Log.i(this, "Set ringtone as haptic only: " + isHapticOnly);
-                    }
+                    Log.i(this, "Set ringtone as haptic only: " + isHapticOnly);
                 } else {
+                    Log.i(this, "ringer & haptics are off, user missed alerts for call");
                     foregroundCall.setUserMissed(USER_MISSED_NO_VIBRATE);
                     Log.addEvent(foregroundCall, LogUtils.Events.SKIP_VIBRATION,
                             vibratorAttrs);
@@ -437,7 +446,7 @@
                 ringtoneInfoSupplier = () -> mRingtoneFactory.getRingtone(
                         foregroundCall, null, false);
             }
-
+            Log.i(this, "isRingtoneInfoSupplierNull=[%b]", ringtoneInfoSupplier == null);
             // If vibration will be done, reserve the vibrator.
             boolean vibratorReserved = isVibratorEnabled && attributes.shouldRingForContact()
                 && tryReserveVibration(foregroundCall);
@@ -706,12 +715,43 @@
         // AudioManager#getRingerModeInternal which only useful for volume controllers
         boolean zenModeOn = mNotificationManager != null
                 && mNotificationManager.getZenMode() != ZEN_MODE_OFF;
+        maybeGenAnomReportForGetRingerMode(zenModeOn, audioManager);
         return mVibrator.hasVibrator()
                 && mSystemSettingsUtil.isRingVibrationEnabled(context)
                 && (audioManager.getRingerMode() != AudioManager.RINGER_MODE_SILENT
                 || (zenModeOn && shouldRingForContact));
     }
 
+    /**
+     * There are 3 settings for haptics:
+     * - AudioManager.RINGER_MODE_SILENT
+     * - AudioManager.RINGER_MODE_VIBRATE
+     * - AudioManager.RINGER_MODE_NORMAL
+     * If the user does not have {@link AudioManager#RINGER_MODE_SILENT} set, the user should
+     * have haptic feeback
+     *
+     * Note: If DND/ZEN_MODE is on, {@link AudioManager#getRingerMode()} will return
+     * {@link AudioManager#RINGER_MODE_SILENT}, regardless of the user setting. Therefore,
+     * getRingerModeInternal is the source of truth instead of {@link AudioManager#getRingerMode()}.
+     * However, if DND/ZEN_MOD is off, the APIs should return the same value.  Generate an anomaly
+     * report if they diverge.
+     */
+    private void maybeGenAnomReportForGetRingerMode(boolean isZenModeOn, AudioManager am) {
+        if (!mFlags.getRingerModeAnomReport()) {
+            return;
+        }
+        if (!isZenModeOn) {
+            int ringerMode = am.getRingerMode();
+            int ringerModeInternal = am.getRingerModeInternal();
+            if (ringerMode != ringerModeInternal) {
+                Log.i(this, "getRingerMode=[%d], getRingerModeInternal=[%d]",
+                        ringerMode, ringerModeInternal);
+                mAnomalyReporter.reportAnomaly(GET_RINGER_MODE_ANOMALY_UUID,
+                        GET_RINGER_MODE_ANOMALY_MSG);
+            }
+        }
+    }
+
     private RingerAttributes getRingerAttributes(Call call, boolean isHfpDeviceAttached) {
         mAudioManager = mContext.getSystemService(AudioManager.class);
         RingerAttributes.Builder builder = new RingerAttributes.Builder();
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index a662dde..88adf14 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -52,6 +52,8 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.OutcomeReceiver;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
@@ -81,6 +83,7 @@
 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.components.UserCallIntentProcessorFactory;
 import com.android.server.telecom.flags.FeatureFlags;
 import com.android.server.telecom.metrics.ApiStats;
@@ -102,6 +105,7 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
 
 // TODO: Needed for move to system service: import com.android.internal.R;
 
@@ -160,6 +164,7 @@
     private final FeatureFlags mFeatureFlags;
     private final com.android.internal.telephony.flags.FeatureFlags mTelephonyFeatureFlags;
     private final TelecomMetricsController mMetricsController;
+    private final String mSystemUiPackageName;
     private AnomalyReporterAdapter mAnomalyReporter = new AnomalyReporterAdapterImpl();
     private final Context mContext;
     private final AppOpsManager mAppOpsManager;
@@ -192,65 +197,64 @@
                 extras.putInt(CallAttributes.CALLER_UID_KEY, Binder.getCallingUid());
                 extras.putInt(CallAttributes.CALLER_PID_KEY, Binder.getCallingPid());
 
-                CallTransaction transaction = null;
-                // create transaction based on the call direction
-                switch (callAttributes.getDirection()) {
-                    case DIRECTION_OUTGOING:
-                        transaction = new OutgoingCallTransaction(callId, mContext, callAttributes,
-                                mCallsManager, extras, mFeatureFlags);
-                        break;
-                    case DIRECTION_INCOMING:
-                        transaction = new IncomingCallTransaction(callId, callAttributes,
-                                mCallsManager, extras, mFeatureFlags);
-                        break;
-                    default:
-                        throw new IllegalArgumentException(String.format("Invalid Call Direction. "
-                                        + "Was [%d] but should be within [%d,%d]",
-                                callAttributes.getDirection(), DIRECTION_INCOMING,
-                                DIRECTION_OUTGOING));
+
+                CompletableFuture<CallTransaction> transactionFuture;
+                long token = Binder.clearCallingIdentity();
+                try {
+                    transactionFuture = mCallsManager.createTransactionalCall(callId,
+                            callAttributes, extras, callingPackage);
+                } finally {
+                    Binder.restoreCallingIdentity(token);
                 }
 
-                mTransactionManager.addTransaction(transaction, new OutcomeReceiver<>() {
-                    @Override
-                    public void onResult(CallTransactionResult result) {
-                        Log.d(TAG, "addCall: onResult");
-                        Call call = result.getCall();
+                transactionFuture.thenCompose((transaction) -> {
+                    if (transaction != null) {
+                        mTransactionManager.addTransaction(transaction, new OutcomeReceiver<>() {
+                            @Override
+                            public void onResult(CallTransactionResult result) {
+                                Log.d(TAG, "addCall: onResult");
+                                Call call = result.getCall();
 
-                        if (call == null || !call.getId().equals(callId)) {
-                            Log.i(TAG, "addCall: onResult: call is null or id mismatch");
-                            onAddCallControl(callId, callEventCallback, null,
-                                    new CallException(ADD_CALL_ERR_MSG, CODE_ERROR_UNKNOWN));
-                            return;
-                        }
+                                if (call == null || !call.getId().equals(callId)) {
+                                    Log.i(TAG, "addCall: onResult: call is null or id mismatch");
+                                    onAddCallControl(callId, callEventCallback, null,
+                                            new CallException(ADD_CALL_ERR_MSG,
+                                                    CODE_ERROR_UNKNOWN));
+                                    return;
+                                }
 
-                        TransactionalServiceWrapper serviceWrapper =
-                                mTransactionalServiceRepository
-                                        .addNewCallForTransactionalServiceWrapper(handle,
-                                                callEventCallback, mCallsManager, call);
+                                TransactionalServiceWrapper serviceWrapper =
+                                        mTransactionalServiceRepository
+                                                .addNewCallForTransactionalServiceWrapper(handle,
+                                                        callEventCallback, mCallsManager, call);
 
-                        call.setTransactionServiceWrapper(serviceWrapper);
+                                call.setTransactionServiceWrapper(serviceWrapper);
 
-                        if (mFeatureFlags.transactionalVideoState()) {
-                            call.setTransactionalCallSupportsVideoCalling(callAttributes);
-                        }
-                        ICallControl clientCallControl = serviceWrapper.getICallControl();
+                                if (mFeatureFlags.transactionalVideoState()) {
+                                    call.setTransactionalCallSupportsVideoCalling(callAttributes);
+                                }
+                                ICallControl clientCallControl = serviceWrapper.getICallControl();
 
-                        if (clientCallControl == null) {
-                            throw new IllegalStateException("TransactionalServiceWrapper"
-                                    + "#ICallControl is null.");
-                        }
+                                if (clientCallControl == null) {
+                                    throw new IllegalStateException("TransactionalServiceWrapper"
+                                            + "#ICallControl is null.");
+                                }
 
-                        // finally, send objects back to the client
-                        onAddCallControl(callId, callEventCallback, clientCallControl, null);
+                                // finally, send objects back to the client
+                                onAddCallControl(callId, callEventCallback, clientCallControl,
+                                        null);
+                            }
+
+                            @Override
+                            public void onError(@NonNull CallException exception) {
+                                Log.d(TAG, "addCall: onError: e=[%s]", exception.toString());
+                                onAddCallControl(callId, callEventCallback, null, exception);
+                            }
+                        });
                     }
-
-                    @Override
-                    public void onError(@NonNull CallException exception) {
-                        Log.d(TAG, "addCall: onError: e=[%s]", exception.toString());
-                        onAddCallControl(callId, callEventCallback, null, exception);
-                    }
+                    event.setResult(ApiStats.RESULT_NORMAL);
+                    return CompletableFuture.completedFuture(transaction);
                 });
-                event.setResult(ApiStats.RESULT_NORMAL);
             } finally {
                 logEvent(event);
                 Log.endSession();
@@ -1437,7 +1441,7 @@
 
                 // ensure the callingPackage is not spoofed
                 // skip check for privileged UIDs and throw SE if package does not match records
-                if (!isPrivilegedUid(callingPackage)
+                if (!isPrivilegedUid()
                         && !callingUidMatchesPackageManagerRecords(callingPackage)) {
                     EventLog.writeEvent(0x534e4554, "236813210", Binder.getCallingUid(),
                             "getCallStateUsingPackage");
@@ -1470,17 +1474,36 @@
             }
         }
 
-        private boolean isPrivilegedUid(String callingPackage) {
+        private boolean isPrivilegedUid() {
             int callingUid = Binder.getCallingUid();
-            boolean isPrivileged = false;
-            switch (callingUid) {
-                case Process.ROOT_UID:
-                case Process.SYSTEM_UID:
-                case Process.SHELL_UID:
-                    isPrivileged = true;
-                    break;
+            return mFeatureFlags.allowSystemAppsResolveVoipCalls()
+                    ? (UserHandle.isSameApp(callingUid, Process.ROOT_UID)
+                            || UserHandle.isSameApp(callingUid, Process.SYSTEM_UID)
+                            || UserHandle.isSameApp(callingUid, Process.SHELL_UID))
+                    : (callingUid == Process.ROOT_UID
+                            || callingUid == Process.SYSTEM_UID
+                            || callingUid == Process.SHELL_UID);
+        }
+
+        private boolean isSysUiUid() {
+            int callingUid = Binder.getCallingUid();
+            int systemUiUid;
+            if (mPackageManager != null && mSystemUiPackageName != null) {
+                try {
+                    systemUiUid = mPackageManager.getPackageUid(mSystemUiPackageName, 0);
+                    Log.i(TAG, "isSysUiUid: callingUid = " + callingUid + "; systemUiUid = "
+                            + systemUiUid);
+                    return UserHandle.isSameApp(callingUid, systemUiUid);
+                } catch (PackageManager.NameNotFoundException e) {
+                    Log.w(TAG, "isSysUiUid: caught PackageManager NameNotFoundException = " + e);
+                    return false;
+                }
+            } else {
+                Log.w(TAG, "isSysUiUid: caught null check and returned false; "
+                        + "mPackageManager = " + mPackageManager + "; mSystemUiPackageName = "
+                        + mSystemUiPackageName);
             }
-            return isPrivileged;
+            return false;
         }
 
         /**
@@ -1496,11 +1519,18 @@
                     if (!enforceAnswerCallPermission(callingPackage, Binder.getCallingUid())) {
                         throw new SecurityException("requires ANSWER_PHONE_CALLS permission");
                     }
-
+                    // Legacy behavior is to ignore whether the invocation is from a system app:
+                    boolean isCallerPrivileged = false;
+                    if (mFeatureFlags.allowSystemAppsResolveVoipCalls()) {
+                        isCallerPrivileged = isPrivilegedUid() || isSysUiUid();
+                        Log.i(TAG, "endCall: Binder.getCallingUid = [" +
+                                Binder.getCallingUid() + "] isCallerPrivileged = " +
+                                isCallerPrivileged);
+                    }
                     long token = Binder.clearCallingIdentity();
                     event.setResult(ApiStats.RESULT_NORMAL);
                     try {
-                        return endCallInternal(callingPackage);
+                        return endCallInternal(callingPackage, isCallerPrivileged);
                     } finally {
                         Binder.restoreCallingIdentity(token);
                     }
@@ -1522,11 +1552,19 @@
                 Log.startSession("TSI.aRC", Log.getPackageAbbreviation(packageName));
                 synchronized (mLock) {
                     if (!enforceAnswerCallPermission(packageName, Binder.getCallingUid())) return;
-
+                    // Legacy behavior is to ignore whether the invocation is from a system app:
+                    boolean isCallerPrivileged = false;
+                    if (mFeatureFlags.allowSystemAppsResolveVoipCalls()) {
+                        isCallerPrivileged = isPrivilegedUid() || isSysUiUid();
+                        Log.i(TAG, "acceptRingingCall: Binder.getCallingUid = [" +
+                                Binder.getCallingUid() + "] isCallerPrivileged = " +
+                                isCallerPrivileged);
+                    }
                     long token = Binder.clearCallingIdentity();
                     event.setResult(ApiStats.RESULT_NORMAL);
                     try {
-                        acceptRingingCallInternal(DEFAULT_VIDEO_STATE, packageName);
+                        acceptRingingCallInternal(DEFAULT_VIDEO_STATE, packageName,
+                                isCallerPrivileged);
                     } finally {
                         Binder.restoreCallingIdentity(token);
                     }
@@ -1549,11 +1587,18 @@
                 Log.startSession("TSI.aRCWVS", Log.getPackageAbbreviation(packageName));
                 synchronized (mLock) {
                     if (!enforceAnswerCallPermission(packageName, Binder.getCallingUid())) return;
-
+                    // Legacy behavior is to ignore whether the invocation is from a system app:
+                    boolean isCallerPrivileged = false;
+                    if (mFeatureFlags.allowSystemAppsResolveVoipCalls()) {
+                        isCallerPrivileged = isPrivilegedUid() || isSysUiUid();
+                        Log.i(TAG, "acceptRingingCallWithVideoState: Binder.getCallingUid = "
+                                + "[" + Binder.getCallingUid() + "] isCallerPrivileged = " +
+                                isCallerPrivileged);
+                    }
                     long token = Binder.clearCallingIdentity();
                     event.setResult(ApiStats.RESULT_NORMAL);
                     try {
-                        acceptRingingCallInternal(videoState, packageName);
+                        acceptRingingCallInternal(videoState, packageName, isCallerPrivileged);
                     } finally {
                         Binder.restoreCallingIdentity(token);
                     }
@@ -2918,7 +2963,8 @@
             SettingsSecureAdapter settingsSecureAdapter,
             FeatureFlags featureFlags,
             com.android.internal.telephony.flags.FeatureFlags telephonyFeatureFlags,
-            TelecomSystem.SyncRoot lock, TelecomMetricsController metricsController) {
+            TelecomSystem.SyncRoot lock, TelecomMetricsController metricsController,
+            String sysUiPackageName) {
         mContext = context;
         mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
 
@@ -2940,6 +2986,7 @@
         mSubscriptionManagerAdapter = subscriptionManagerAdapter;
         mSettingsSecureAdapter = settingsSecureAdapter;
         mMetricsController = metricsController;
+        mSystemUiPackageName = sysUiPackageName;
 
         mDefaultDialerCache.observeDefaultDialerApplication(mContext.getMainExecutor(), userId -> {
             String defaultDialer = mDefaultDialerCache.getDefaultDialerApplication(userId);
@@ -3054,13 +3101,14 @@
         return false;
     }
 
-    private void acceptRingingCallInternal(int videoState, String packageName) {
+    private void acceptRingingCallInternal(int videoState, String packageName,
+            boolean isCallerPrivileged) {
         Call call = mCallsManager.getFirstCallWithState(CallState.RINGING,
                 CallState.SIMULATED_RINGING);
         if (call != null) {
-            if (call.isSelfManaged()) {
+            if (call.isSelfManaged() && !isCallerPrivileged) {
                 Log.addEvent(call, LogUtils.Events.REQUEST_ACCEPT,
-                        "self-mgd accept ignored from " + packageName);
+                        "self-mgd accept ignored from non-privileged app " + packageName);
                 return;
             }
 
@@ -3075,7 +3123,7 @@
     // Supporting methods for the ITelecomService interface implementation.
     //
 
-    private boolean endCallInternal(String callingPackage) {
+    private boolean endCallInternal(String callingPackage, boolean isCallerPrivileged) {
         // Always operate on the foreground call if one exists, otherwise get the first call in
         // priority order by call-state.
         Call call = mCallsManager.getForegroundCall();
@@ -3095,9 +3143,10 @@
                 return false;
             }
 
-            if (call.isSelfManaged()) {
+            if (call.isSelfManaged() && !isCallerPrivileged) {
                 Log.addEvent(call, LogUtils.Events.REQUEST_DISCONNECT,
-                        "self-mgd disconnect ignored from " + callingPackage);
+                        "self-mgd disconnect ignored from non-privileged app " +
+                                callingPackage);
                 return false;
             }
 
@@ -3600,10 +3649,11 @@
         // Note: Important to clear the calling identity since the code below calls into RoleManager
         // to check who holds the dialer role, and that requires MANAGE_ROLE_HOLDERS permission
         // which is a system permission.
+        int callingUserId = Binder.getCallingUserHandle().getIdentifier();
         long token = Binder.clearCallingIdentity();
         try {
             return mDefaultDialerCache.isDefaultOrSystemDialer(
-                    callingPackage, Binder.getCallingUserHandle().getIdentifier());
+                    callingPackage, callingUserId);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index 94bea42..7020885 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -224,6 +224,7 @@
             RoleManagerAdapter roleManagerAdapter,
             ContactsAsyncHelper.Factory contactsAsyncHelperFactory,
             DeviceIdleControllerAdapter deviceIdleControllerAdapter,
+            String sysUiPackageName,
             Ringer.AccessibilityManagerAdapter accessibilityManagerAdapter,
             Executor asyncTaskExecutor,
             Executor asyncCallAudioTaskExecutor,
@@ -245,8 +246,8 @@
         // Wrap this in a try block to ensure session cleanup occurs in the case of error.
         try {
             mPhoneAccountRegistrar = new PhoneAccountRegistrar(mContext, mLock, defaultDialerCache,
-                    packageName -> AppLabelProxy.Util.getAppLabel(
-                            mContext.getPackageManager(), packageName), null, mFeatureFlags);
+                    (packageName, userHandle) -> AppLabelProxy.Util.getAppLabel(mContext,
+                            userHandle, packageName, mFeatureFlags), null, mFeatureFlags);
 
             mContactsAsyncHelper = contactsAsyncHelperFactory.create(
                     new ContactsAsyncHelper.ContentResolverAdapter() {
@@ -386,8 +387,8 @@
 
             CallStreamingNotification callStreamingNotification =
                     new CallStreamingNotification(mContext,
-                            packageName -> AppLabelProxy.Util.getAppLabel(
-                                    mContext.getPackageManager(), packageName), asyncTaskExecutor);
+                            (packageName, userHandle) -> AppLabelProxy.Util.getAppLabel(mContext,
+                                    userHandle, packageName, mFeatureFlags), asyncTaskExecutor);
 
             mCallsManager = new CallsManager(
                     mContext,
@@ -503,7 +504,8 @@
                     featureFlags,
                     null,
                     mLock,
-                    metricsController);
+                    metricsController,
+                    sysUiPackageName);
         } finally {
             Log.endSession();
         }
diff --git a/src/com/android/server/telecom/TransactionalServiceWrapper.java b/src/com/android/server/telecom/TransactionalServiceWrapper.java
index cf5ef41..d63a0bd 100644
--- a/src/com/android/server/telecom/TransactionalServiceWrapper.java
+++ b/src/com/android/server/telecom/TransactionalServiceWrapper.java
@@ -401,11 +401,12 @@
         return onSetActiveFuture;
     }
 
-    public void onAnswer(Call call, int videoState) {
+    public CompletableFuture<Boolean> onAnswer(Call call, int videoState) {
+        CompletableFuture<Boolean> onAnswerFuture;
         try {
             Log.startSession("TSW.oA");
             Log.d(TAG, String.format(Locale.US, "onAnswer: callId=[%s]", call.getId()));
-            mCallSequencingAdapter.onSetAnswered(call, videoState,
+            onAnswerFuture = mCallSequencingAdapter.onSetAnswered(call, videoState,
                     new CallEventCallbackAckTransaction(mICallEventCallback,
                             ON_ANSWER, call.getId(), videoState, mLock),
                     result -> Log.i(TAG, String.format(Locale.US,
@@ -414,6 +415,7 @@
         } finally {
             Log.endSession();
         }
+        return onAnswerFuture;
     }
 
     public CompletableFuture<Boolean> onSetInactive(Call call) {
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
index 176e479..550a815 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
@@ -34,6 +34,7 @@
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
 import android.os.Bundle;
+import android.telecom.CallAudioState;
 import android.telecom.Log;
 import android.util.ArraySet;
 import android.util.LocalLog;
@@ -233,7 +234,8 @@
                 }
            };
 
-    private void handleAudioRefactoringServiceDisconnected(int profile) {
+    @VisibleForTesting
+    public void handleAudioRefactoringServiceDisconnected(int profile) {
         CallAudioRouteController controller = (CallAudioRouteController)
                 mCallAudioRouteAdapter;
         Map<AudioRoute, BluetoothDevice> btRoutes = controller
@@ -257,8 +259,23 @@
             mCallAudioRouteAdapter.sendMessageWithSessionInfo(
                     BT_DEVICE_REMOVED, route.getType(), device);
         }
-        mCallAudioRouteAdapter.sendMessageWithSessionInfo(
-                SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE, (String) null);
+
+        if (mFeatureFlags.skipBaselineSwitchWhenRouteNotBluetooth()) {
+            CallAudioState currentAudioState = controller.getCurrentCallAudioState();
+            int currentRoute = currentAudioState.getRoute();
+            if (currentRoute == CallAudioState.ROUTE_BLUETOOTH) {
+                Log.d(this, "handleAudioRefactoringServiceDisconnected: call audio "
+                        + "is currently routed to BT so switching back to baseline");
+                mCallAudioRouteAdapter.sendMessageWithSessionInfo(
+                        SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE, (String) null);
+            } else {
+                Log.d(this, "handleAudioRefactoringServiceDisconnected: call audio "
+                        + "is not currently routed to BT so skipping switch to baseline");
+            }
+        } else {
+            mCallAudioRouteAdapter.sendMessageWithSessionInfo(
+                    SWITCH_BASELINE_ROUTE, INCLUDE_BLUETOOTH_IN_BASELINE, (String) null);
+        }
     }
 
     private final LinkedHashMap<String, BluetoothDevice> mHfpDevicesByAddress =
@@ -308,19 +325,19 @@
         mFeatureFlags = featureFlags;
         if (bluetoothAdapter != null) {
             mBluetoothAdapter = bluetoothAdapter;
-            if (mFeatureFlags.useRefactoredAudioRouteSwitching()) {
-                mBluetoothHeadsetFuture = new CompletableFuture<>();
-            }
             bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener,
                     BluetoothProfile.HEADSET);
             bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener,
                     BluetoothProfile.HEARING_AID);
             bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener,
                     BluetoothProfile.LE_AUDIO);
-            mAudioManager = context.getSystemService(AudioManager.class);
-            mExecutor = context.getMainExecutor();
-            mCommunicationDeviceTracker = communicationDeviceTracker;
         }
+        if (mFeatureFlags.useRefactoredAudioRouteSwitching()) {
+            mBluetoothHeadsetFuture = new CompletableFuture<>();
+        }
+        mAudioManager = context.getSystemService(AudioManager.class);
+        mExecutor = context.getMainExecutor();
+        mCommunicationDeviceTracker = communicationDeviceTracker;
     }
 
     public void setBluetoothRouteManager(BluetoothRouteManager brm) {
@@ -519,7 +536,10 @@
                 Log.i(this, "onDeviceConnected: Adding device with address: %s and devicetype=%s",
                         device, getDeviceTypeString(deviceType));
                 targetDeviceMap.put(device.getAddress(), device);
-                mBluetoothRouteManager.onDeviceAdded(device.getAddress());
+                if (!mFeatureFlags.keepBluetoothDevicesCacheUpdated()
+                        || !mFeatureFlags.useRefactoredAudioRouteSwitching()) {
+                    mBluetoothRouteManager.onDeviceAdded(device.getAddress());
+                }
             }
         }
     }
@@ -551,7 +571,10 @@
                 Log.i(this, "onDeviceDisconnected: Removing device with address: %s, devicetype=%s",
                         device, getDeviceTypeString(deviceType));
                 targetDeviceMap.remove(device.getAddress());
-                mBluetoothRouteManager.onDeviceLost(device.getAddress());
+                if (!mFeatureFlags.keepBluetoothDevicesCacheUpdated()
+                        || !mFeatureFlags.useRefactoredAudioRouteSwitching()) {
+                    mBluetoothRouteManager.onDeviceLost(device.getAddress());
+                }
             }
         }
     }
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
index 7667ebc..1cea531 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
@@ -211,6 +211,9 @@
             if (mFeatureFlags.useRefactoredAudioRouteSwitching()) {
                 mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_DEVICE_ADDED,
                         audioRouteType, device);
+                if (mFeatureFlags.keepBluetoothDevicesCacheUpdated()) {
+                    mBluetoothDeviceManager.onDeviceConnected(device, deviceType);
+                }
             } else {
                 mBluetoothDeviceManager.onDeviceConnected(device, deviceType);
             }
@@ -219,6 +222,9 @@
             if (mFeatureFlags.useRefactoredAudioRouteSwitching()) {
                 mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_DEVICE_REMOVED,
                         audioRouteType, device);
+                if (mFeatureFlags.keepBluetoothDevicesCacheUpdated()) {
+                    mBluetoothDeviceManager.onDeviceDisconnected(device, deviceType);
+                }
             } else {
                 mBluetoothDeviceManager.onDeviceDisconnected(device, deviceType);
             }
@@ -252,17 +258,14 @@
             CallAudioRouteController audioRouteController = (CallAudioRouteController)
                     mCallAudioRouteAdapter;
             if (device == null) {
-                if (!mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()) {
-                    audioRouteController.updateActiveBluetoothDevice(
-                            new Pair(audioRouteType, null));
-                }
+                // Update the active device cache immediately.
+                audioRouteController.updateActiveBluetoothDevice(new Pair(audioRouteType, null));
                 mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_GONE,
                         audioRouteType);
             } else {
-                if (!mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()) {
-                    audioRouteController.updateActiveBluetoothDevice(
-                            new Pair(audioRouteType, device.getAddress()));
-                }
+                // Update the active device cache immediately.
+                audioRouteController.updateActiveBluetoothDevice(
+                        new Pair(audioRouteType, device.getAddress()));
                 mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT,
                         audioRouteType, device.getAddress());
                 if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID
diff --git a/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
index f07c0aa..efac87d 100644
--- a/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
+++ b/src/com/android/server/telecom/callfiltering/CallScreeningServiceFilter.java
@@ -269,7 +269,8 @@
         mContext = context;
         mPackageManager = mContext.getPackageManager();
         mCallsManager = callsManager;
-        mAppName = appLabelProxy.getAppLabel(mPackageName);
+        mAppName = appLabelProxy.getAppLabel(mPackageName,
+                mCall.getAssociatedUser());
         mParcelableCallUtilsConverter = parcelableCallUtilsConverter;
     }
 
diff --git a/src/com/android/server/telecom/callsequencing/CallSequencingController.java b/src/com/android/server/telecom/callsequencing/CallSequencingController.java
index 2f0ae45..2794496 100644
--- a/src/com/android/server/telecom/callsequencing/CallSequencingController.java
+++ b/src/com/android/server/telecom/callsequencing/CallSequencingController.java
@@ -16,67 +16,813 @@
 
 package com.android.server.telecom.callsequencing;
 
+import static android.Manifest.permission.CALL_PRIVILEGED;
+import static android.telecom.CallException.CODE_ERROR_UNKNOWN;
+
+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.OUTGOING_CALL_STATES;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.OutcomeReceiver;
+import android.telecom.CallAttributes;
+import android.telecom.CallException;
+import android.telecom.DisconnectCause;
+import android.telecom.Log;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telephony.AnomalyReporter;
 
+import androidx.annotation.NonNull;
+
+import com.android.internal.telecom.ICallControl;
 import com.android.server.telecom.Call;
+import com.android.server.telecom.CallState;
 import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.LogUtils;
+import com.android.server.telecom.LoggedHandlerExecutor;
+import com.android.server.telecom.TransactionalServiceWrapper;
+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.stats.CallFailureCause;
 
 import java.util.concurrent.CompletableFuture;
 
 /**
  * Controls the sequencing between calls when moving between the user ACTIVE (RINGING/ACTIVE) and
- * user INACTIVE (INCOMING/HOLD/DISCONNECTED) states.
+ * user INACTIVE (INCOMING/HOLD/DISCONNECTED) states. This controller is gated by the
+ * {@link FeatureFlags#enableCallSequencing()} flag. Call state changes are verified on a
+ * transactional basis where each operation is verified step by step for cross-phone account calls
+ * or just for the focus call in the case of processing calls on the same phone account.
  */
 public class CallSequencingController {
-//    private final CallsManager mCallsManager;
+    private final CallsManager mCallsManager;
+    private final Handler mHandler;
+    private final Context mContext;
+    private final FeatureFlags mFeatureFlags;
     private final TransactionManager mTransactionManager;
-//    private final Handler mHandler;
-//    private boolean mCallSequencingEnabled;
+    private boolean mProcessingCallSequencing;
 
-    public CallSequencingController(CallsManager callsManager, boolean callSequencingEnabled) {
-//        mCallsManager = callsManager;
-        mTransactionManager = TransactionManager.getInstance();
+    public CallSequencingController(CallsManager callsManager, Context context,
+            FeatureFlags featureFlags) {
+        mCallsManager = callsManager;
         HandlerThread handlerThread = new HandlerThread(this.toString());
         handlerThread.start();
-//        mHandler = new Handler(handlerThread.getLooper());
-//        mCallSequencingEnabled = callSequencingEnabled;
+        mHandler = new Handler(handlerThread.getLooper());
+        mProcessingCallSequencing = false;
+        mFeatureFlags = featureFlags;
+        mContext = context;
+        mTransactionManager = TransactionManager.getInstance();;
     }
 
+    /**
+     * Creates the outgoing call transaction given that call sequencing is enabled. Two separate
+     * transactions are being tracked here; one is if room needs to be made for the outgoing call
+     * and another to verify that the new call was placed. We need to ensure that the transaction
+     * to make room for the outgoing call is processed beforehand (i.e. see
+     * {@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) {
+        PhoneAccountHandle requestedAccountHandle = callAttributes.getPhoneAccountHandle();
+        Uri address = callAttributes.getAddress();
+        if (mCallsManager.isOutgoingCallPermitted(requestedAccountHandle)) {
+            Log.d(this, "createTransactionalOutgoingCall: outgoing call permitted");
+            final boolean hasCallPrivilegedPermission = mContext.checkCallingPermission(
+                    CALL_PRIVILEGED) == PackageManager.PERMISSION_GRANTED;
+
+            final Intent intent = new Intent(hasCallPrivilegedPermission ?
+                    Intent.ACTION_CALL_PRIVILEGED : Intent.ACTION_CALL, address);
+            Bundle updatedExtras = OutgoingCallTransaction.generateExtras(callId, extras,
+                    callAttributes, mFeatureFlags);
+            // Note that this may start a potential transaction to make room for the outgoing call
+            // so we want to ensure that transaction is queued up first and then create another
+            // transaction to complete the call future.
+            CompletableFuture<Call> callFuture = mCallsManager.startOutgoingCall(address,
+                    requestedAccountHandle, updatedExtras, requestedAccountHandle.getUserHandle(),
+                    intent, callingPackage);
+            // The second transaction is represented below which will contain the result of whether
+            // the new outgoing call was placed or not. To simplify the logic, we will wait on the
+            // result of the outgoing call future before adding the transaction so that we can wait
+            // for the make room future to complete first.
+            if (callFuture == null) {
+                Log.d(this, "createTransactionalOutgoingCall: Outgoing call not permitted at the "
+                        + "current time.");
+                return CompletableFuture.completedFuture(null);
+            }
+            return callFuture.thenComposeAsync((call) -> CompletableFuture.completedFuture(
+                    new OutgoingCallTransactionSequencing(mCallsManager, callFuture,
+                            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);
+        }
+    }
+
+    /**
+     * Processes the answer call request from the app and verifies the call state changes with
+     * sequencing provided that the calls that are being manipulated are across phone accounts.
+     * @param incomingCall The incoming call to be answered.
+     * @param videoState The video state configuration for the provided call.
+     */
     public void answerCall(Call incomingCall, int videoState) {
-        // Todo: call sequencing logic (stubbed)
+        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) -> {
+                if (result) {
+                    mCallsManager.requestFocusActionAnswerCall(incomingCall, videoState);
+                } else {
+                    Log.i(this, "answerCall: Hold active call transaction failed. Aborting "
+                            + "request to answer the incoming call.");
+                }
+                return CompletableFuture.completedFuture(result);
+            }, new LoggedHandlerExecutor(mHandler, "CSC.aC",
+                    mCallsManager.getLock()));
+        } else {
+            mCallsManager.requestFocusActionAnswerCall(incomingCall, videoState);
+        }
+        resetProcessingCallSequencing();
     }
 
-//    private CompletableFuture<Boolean> holdActiveCallForNewCallWithSequencing(Call call) {
-//        // Todo: call sequencing logic (stubbed)
-//        return null;
-//    }
+    /**
+     * Handles the case of setting a self-managed call active with call sequencing support.
+     * @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) -> {
+                if (result) {
+                    Log.i(this, "markCallAsActive: requesting focus for self managed call "
+                            + "before setting active.");
+                    mCallsManager.requestActionSetActiveCall(call,
+                            "active set explicitly for self-managed");
+                } else {
+                    Log.i(this, "markCallAsActive: Unable to hold active call. "
+                            + "Aborting transaction to set self managed call active.");
+                }
+                return CompletableFuture.completedFuture(result);
+            }, new LoggedHandlerExecutor(mHandler,
+                    "CM.mCAA", mCallsManager.getLock()));
+        } else {
+            mCallsManager.requestActionSetActiveCall(call,
+                    "active set explicitly for self-managed");
+        }
+        resetProcessingCallSequencing();
+    }
 
+    /**
+     * This applies to transactional calls which request to hold the active call with call
+     * sequencing support. The resulting future is an indication of whether the hold request
+     * succeeded which is then used to create additional transactions to request call focus for the
+     * new call.
+     * @param newCall The new transactional call that's waiting to go active.
+     * @param callback The callback used to report the result of holding the active call and if
+     *                 the new call can go active.
+     * @return The {@code CompletableFuture} indicating the result of holding the active call
+     *         (if applicable).
+     */
+    public void transactionHoldPotentialActiveCallForNewCallSequencing(
+            Call newCall, OutcomeReceiver<Boolean, CallException> callback) {
+        CompletableFuture<Boolean> holdActiveCallFuture =
+                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.
+                        callback.onResult(true);
+                    } else {
+                        Log.i(this, "transactionHoldPotentialActiveCallForNewCallSequencing: "
+                                + "active call could not be held or disconnected");
+                        callback.onError(
+                                new CallException("activeCall could not be held or disconnected",
+                                CallException.CODE_CANNOT_HOLD_CURRENT_ACTIVE_CALL));
+                    }
+                    return CompletableFuture.completedFuture(result);
+                }, new LoggedHandlerExecutor(mHandler, "CM.mCAA", mCallsManager.getLock()));
+        resetProcessingCallSequencing();
+    }
+
+    /**
+     * Attempts to hold the active call so that the provided call can go active. This is done via
+     * 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).
+     */
+    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);
+            if (mCallsManager.canHold(activeCall)) {
+                return activeCall.hold("swap to " + call.getId());
+            } 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
+                // holding the active call.
+                // E.g.
+                // Call A - Held   (Supports hold, can't hold)
+                // Call B - Active (Supports hold, can't hold)
+                // Call C - Incoming
+                // Here we need to disconnect A prior to holding B so that C can be answered.
+                // This case is driven by telephony requirements ultimately.
+                //
+                // These cases can further be broken down at the phone account level:
+                // E.g. All cases not outlined below...
+                // (1)                              (2)
+                // Call A (Held) - PA1              Call A (Held) - PA1
+                // Call B (Active) - PA2            Call B (Active) - PA2
+                // Call C (Incoming) - PA1          Call C (Incoming) - PA2
+                // We should ensure that only operations across phone accounts require sequencing.
+                // 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 areIncomingHeldFromSameSource;
+
+                if (heldCall != null) {
+                    processCallSequencing(heldCall, activeCall);
+                    processCallSequencing(call, heldCall);
+                    areIncomingHeldFromSameSource = CallsManager.areFromSameSource(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 (areIncomingHeldFromSameSource || !(call.isSelfManaged()
+                            && !heldCall.isSelfManaged())) {
+                        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) {
+                    holdFutureHandler = disconnectFutureHandler
+                            .thenComposeAsync((result) -> {
+                                if (result) {
+                                    return activeCall.hold();
+                                }
+                                return CompletableFuture.completedFuture(false);
+                            }, new LoggedHandlerExecutor(mHandler,
+                                    "CSC.hACFNCWS", mCallsManager.getLock()));
+                } else {
+                    holdFutureHandler = activeCall.hold();
+                }
+                call.increaseHeldByThisCallCount();
+                return holdFutureHandler;
+            } 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();
+                        }
+                    } 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");
+                    }
+                } 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.");
+                }
+            }
+        }
+        return CompletableFuture.completedFuture(true);
+    }
+
+    /**
+     * Processes the unhold call request sent by the app with call sequencing support.
+     * @param call The call to be unheld.
+     */
     public void unholdCall(Call call) {
-        // Todo: call sequencing logic (stubbed)
+        // Cases: set active call on hold and then set this call to active
+        // Calls could be made on different phone accounts, in which case, we need to verify state
+        // change for each call.
+        CompletableFuture<Boolean> unholdCallFutureHandler = null;
+        Call activeCall = (Call) mCallsManager.getConnectionServiceFocusManager()
+                .getCurrentFocusCall();
+        if (activeCall != null && !activeCall.isLocallyDisconnecting()) {
+            // Determine whether the calls are placed on different phone accounts.
+            boolean areFromSameSource = CallsManager.areFromSameSource(activeCall, call);
+            processCallSequencing(activeCall, call);
+            boolean canHoldActiveCall = mCallsManager.canHold(activeCall);
+
+            // If the active + held call are from different phone accounts, ensure that the call
+            // sequencing states are verified at each step.
+            if (canHoldActiveCall) {
+                unholdCallFutureHandler = activeCall.hold("Swap to " + call.getId());
+                Log.addEvent(activeCall, LogUtils.Events.SWAP, "To " + call.getId());
+                Log.addEvent(call, LogUtils.Events.SWAP, "From " + activeCall.getId());
+            } else {
+                if (!areFromSameSource) {
+                    // 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 (!activeCall.isEmergencyCall()) {
+                        Log.w(this, "unholdCall: % and %s are using different phone accounts. "
+                                        + "Aborting swap to %s", activeCall.getId(), call.getId(),
+                                call.getId());
+                    } else {
+                        Log.w(this, "unholdCall: % is an emergency call, aborting swap to %s",
+                                activeCall.getId(), call.getId());
+                    }
+                    return;
+                } else {
+                    activeCall.hold("Swap to " + call.getId());
+                }
+            }
+        }
+
+        // Verify call state was changed to ACTIVE state
+        if (isProcessingCallSequencing() && unholdCallFutureHandler != null) {
+            // Only attempt to unhold call if previous request to hold/disconnect call (on different
+            // phone account) succeeded.
+            unholdCallFutureHandler.thenComposeAsync((result) -> {
+                if (result) {
+                    Log.i(this, "unholdCall: Request to hold active call transaction succeeded.");
+                    mCallsManager.requestActionUnholdCall(call, activeCall.getId());
+                } else {
+                    Log.i(this, "unholdCall: Request to hold active call transaction failed. "
+                            + "Aborting unhold transaction.");
+                }
+                return CompletableFuture.completedFuture(result);
+            }, new LoggedHandlerExecutor(mHandler, "CSC.uC",
+                    mCallsManager.getLock()));
+        } else {
+            // Otherwise, we should verify call unhold succeeded for focus call.
+            mCallsManager.requestActionUnholdCall(call, activeCall.getId());
+        }
+        resetProcessingCallSequencing();
     }
 
     public CompletableFuture<Boolean> makeRoomForOutgoingCall(boolean isEmergency, Call call) {
-        // Todo: call sequencing logic (stubbed)
-        return CompletableFuture.completedFuture(true);
-//        return isEmergency ? makeRoomForOutgoingEmergencyCall(call) : makeRoomForOutgoingCall(call);
+        CompletableFuture<Boolean> makeRoomForOutgoingCallFuture = isEmergency
+                ? makeRoomForOutgoingEmergencyCall(call)
+                : makeRoomForOutgoingCall(call);
+        resetProcessingCallSequencing();
+        return makeRoomForOutgoingCallFuture;
     }
 
-//    private CompletableFuture<Boolean> makeRoomForOutgoingEmergencyCall(Call emergencyCall) {
-//        // Todo: call sequencing logic (stubbed)
-//        return CompletableFuture.completedFuture(true);
-//    }
+    /**
+     * This function tries to make room for the new emergency outgoing call via call sequencing.
+     * The resulting future is an indication of whether room was able to be made for the emergency
+     * call if needed.
+     * @param emergencyCall The outgoing emergency call to be placed.
+     * @return The {@code CompletableFuture} indicating the result of whether room was able to be
+     *         made for the emergency call.
+     */
+    private CompletableFuture<Boolean> makeRoomForOutgoingEmergencyCall(Call emergencyCall) {
+        // Always disconnect any ringing/incoming calls when an emergency call is placed to minimize
+        // distraction. This does not affect live call count.
+        CompletableFuture<Boolean> ringingCallFuture = null;
+        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) {
+                if (!ringingCall.hasGoneActiveBefore()) {
+                    // If this is an incoming call that is currently in SIMULATED_RINGING only
+                    // after a call screen, disconnect to make room and mark as missed, since
+                    // the user didn't get a chance to accept/reject.
+                    ringingCallFuture = ringingCall.disconnect("emergency call dialed during "
+                            + "simulated ringing after screen.");
+                } else {
+                    // If this is a simulated ringing call after being active and put in
+                    // AUDIO_PROCESSING state again, disconnect normally.
+                    ringingCallFuture = ringingCall.reject(false, null, "emergency call dialed "
+                            + "during simulated ringing.");
+                }
+            } else { // normal incoming ringing call.
+                // Hang up the ringing call to make room for the emergency call and mark as missed,
+                // since the user did not reject.
+                ringingCall.setOverrideDisconnectCauseCode(
+                        new DisconnectCause(DisconnectCause.MISSED));
+                ringingCallFuture = ringingCall.reject(false, null, "emergency call dialed "
+                        + "during ringing.");
+            }
+        }
 
-//    private CompletableFuture<Boolean> makeRoomForOutgoingCall(Call call) {
-//        // Todo: call sequencing logic (stubbed)
-//        return CompletableFuture.completedFuture(true);
-//    }
+        // There is already room!
+        if (!mCallsManager.hasMaximumLiveCalls(emergencyCall)) {
+            return CompletableFuture.completedFuture(true);
+        }
 
-//    private void resetProcessingCallSequencing() {
-//        mTransactionManager.setProcessingCallSequencing(false);
-//    }
+        Call liveCall = mCallsManager.getFirstCallWithLiveState();
+        Log.i(this, "makeRoomForOutgoingEmergencyCall: call = " + emergencyCall
+                + " livecall = " + liveCall);
 
-    public CompletableFuture<Boolean> disconnectCall() {
-        return CompletableFuture.completedFuture(true);
+        if (emergencyCall == liveCall) {
+            // Not likely, but a good correctness check.
+            return CompletableFuture.completedFuture(true);
+        }
+
+        if (mCallsManager.hasMaximumOutgoingCalls(emergencyCall)) {
+            Call outgoingCall = mCallsManager.getFirstCallWithState(OUTGOING_CALL_STATES);
+            String disconnectReason = null;
+            if (!outgoingCall.isEmergencyCall()) {
+                emergencyCall.getAnalytics().setCallIsAdditional(true);
+                outgoingCall.getAnalytics().setCallIsInterrupted(true);
+                disconnectReason = "Disconnecting dialing call in favor of new dialing"
+                        + " emergency call.";
+            }
+            if (outgoingCall.getState() == CallState.SELECT_PHONE_ACCOUNT) {
+                // Correctness check: if there is an orphaned emergency call in the
+                // {@link CallState#SELECT_PHONE_ACCOUNT} state, just disconnect it since the user
+                // has explicitly started a new call.
+                emergencyCall.getAnalytics().setCallIsAdditional(true);
+                outgoingCall.getAnalytics().setCallIsInterrupted(true);
+                disconnectReason = "Disconnecting call in SELECT_PHONE_ACCOUNT in favor"
+                        + " of new outgoing call.";
+            }
+            if (disconnectReason != null) {
+                processCallSequencing(outgoingCall, emergencyCall);
+                if (ringingCallFuture != null && isProcessingCallSequencing()) {
+                    String finalDisconnectReason = disconnectReason;
+                    return ringingCallFuture.thenComposeAsync((result) -> {
+                        if (result) {
+                            Log.i(this, "makeRoomForOutgoingEmergencyCall: Request to disconnect"
+                                    + " ringing call succeeded. Attempting to disconnect "
+                                    + "outgoing call.");
+                            return outgoingCall.disconnect(finalDisconnectReason);
+                        } else {
+                            Log.i(this, "makeRoomForOutgoingEmergencyCall: Request to disconnect"
+                                    + "ringing call failed. Aborting attempt to disconnect "
+                                    + "outgoing call");
+                            return CompletableFuture.completedFuture(false);
+                        }
+                    }, new LoggedHandlerExecutor(mHandler, "CSC.mRFOEC",
+                            mCallsManager.getLock()));
+                } else {
+                    return outgoingCall.disconnect(disconnectReason);
+                }
+            }
+            //  If the user tries to make two outgoing calls to different emergency call numbers,
+            //  we will try to connect the first outgoing call and reject the second.
+            emergencyCall.setStartFailCause(CallFailureCause.IN_EMERGENCY_CALL);
+            return CompletableFuture.completedFuture(false);
+        }
+
+        processCallSequencing(liveCall, emergencyCall);
+        if (ringingCall != null) {
+            processCallSequencing(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()) {
+                return ringingCallFuture.thenComposeAsync((result) -> {
+                    if (result) {
+                        Log.i(this, "makeRoomForOutgoingEmergencyCall: Request to disconnect "
+                                + "ringing call succeeded. Attempting to disconnect live call.");
+                        return liveCall.disconnect(disconnectReason);
+                    } else {
+                        Log.i(this, "makeRoomForOutgoingEmergencyCall: Request to disconnect "
+                                + "ringing call failed. Aborting attempt to disconnect live call.");
+                        return CompletableFuture.completedFuture(false);
+                    }
+                }, new LoggedHandlerExecutor(mHandler, "CSC.mRFOEC",
+                        mCallsManager.getLock()));
+            } else {
+                return liveCall.disconnect(disconnectReason);
+            }
+        }
+
+        // If the live call is stuck in a connecting state, prompt the user to generate a bugreport.
+        if (liveCall.getState() == CallState.CONNECTING) {
+            AnomalyReporter.reportAnomaly(LIVE_CALL_STUCK_CONNECTING_EMERGENCY_ERROR_UUID,
+                    LIVE_CALL_STUCK_CONNECTING_EMERGENCY_ERROR_MSG);
+        }
+
+        // If we have the max number of held managed calls and we're placing an emergency call,
+        // we'll disconnect the ongoing call if it cannot be held.
+        if (mCallsManager.hasMaximumManagedHoldingCalls(emergencyCall)
+                && !mCallsManager.canHold(liveCall)) {
+            emergencyCall.getAnalytics().setCallIsAdditional(true);
+            liveCall.getAnalytics().setCallIsInterrupted(true);
+            // Disconnect the active call instead of the holding call because it is historically
+            // 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()) {
+                return ringingCallFuture.thenComposeAsync((result) -> {
+                    if (result) {
+                        Log.i(this, "makeRoomForOutgoingEmergencyCall: Request to disconnect "
+                                + "ringing call succeeded. Attempting to disconnect live call.");
+                        return liveCall.disconnect(disconnectReason);
+                    } else {
+                        Log.i(this, "makeRoomForOutgoingEmergencyCall: Request to disconnect "
+                                + "ringing call failed. Aborting attempt to disconnect live call.");
+                        return CompletableFuture.completedFuture(false);
+                    }
+                }, new LoggedHandlerExecutor(mHandler, "CSC.mRFOEC",
+                        mCallsManager.getLock()));
+            } else {
+                return liveCall.disconnect(disconnectReason);
+            }
+        }
+
+        // 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, "makeRoomForOutgoingEmergencyCall: using child call PhoneAccount = " +
+                    liveCallPhoneAccount);
+        }
+
+        // We may not know which PhoneAccount the emergency call will be placed on yet, but if
+        // the liveCall PhoneAccount does not support placing emergency calls, then we know it
+        // 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) {
+            PhoneAccount pa = mCallsManager.getPhoneAccountRegistrar().getPhoneAccountUnchecked(
+                    liveCall.getTargetPhoneAccount());
+            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()) {
+                    return ringingCallFuture.thenComposeAsync((result) -> {
+                        if (result) {
+                            Log.i(this, "makeRoomForOutgoingEmergencyCall: Request to disconnect "
+                                    + "ringing call succeeded. "
+                                    + "Attempting to disconnect live call.");
+                            return liveCall.disconnect(disconnectReason);
+                        } else {
+                            Log.i(this, "makeRoomForOutgoingEmergencyCall: Request to disconnect "
+                                    + "ringing call failed. "
+                                    + "Aborting attempt to disconnect live call.");
+                            return CompletableFuture.completedFuture(false);
+                        }
+                    }, new LoggedHandlerExecutor(mHandler, "CSC.mRFOEC",
+                            mCallsManager.getLock()));
+                } else {
+                    return liveCall.disconnect(disconnectReason);
+                }
+            } else {
+                return CompletableFuture.completedFuture(true);
+            }
+        }
+
+        // First thing, if we are trying to make an emergency call with the same package name as
+        // the live call, then allow it so that the connection service can make its own decision
+        // about how to handle the new call relative to the current one.
+        // By default, for telephony, it will try to hold the existing call before placing the new
+        // emergency call except for if the carrier does not support holding calls for emergency.
+        // In this case, telephony will disconnect the call.
+        if (PhoneAccountHandle.areFromSamePackage(liveCallPhoneAccount,
+                emergencyCall.getTargetPhoneAccount())) {
+            Log.i(this, "makeRoomForOutgoingEmergencyCall: phoneAccount matches.");
+            emergencyCall.getAnalytics().setCallIsAdditional(true);
+            liveCall.getAnalytics().setCallIsInterrupted(true);
+            return CompletableFuture.completedFuture(true);
+        } else if (emergencyCall.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);
+        }
+
+        // Hold the live call if possible before attempting the new outgoing emergency call.
+        if (mCallsManager.canHold(liveCall)) {
+            Log.i(this, "makeRoomForOutgoingEmergencyCall: holding live call.");
+            emergencyCall.getAnalytics().setCallIsAdditional(true);
+            emergencyCall.increaseHeldByThisCallCount();
+            liveCall.getAnalytics().setCallIsInterrupted(true);
+            final String holdReason = "calling " + emergencyCall.getId();
+            if (ringingCallFuture != null && isProcessingCallSequencing()) {
+                return ringingCallFuture.thenComposeAsync((result) -> {
+                    if (result) {
+                        Log.i(this, "makeRoomForOutgoingEmergencyCall: Request to disconnect "
+                                + "ringing call succeeded. Attempting to hold live call.");
+                        return liveCall.hold(holdReason);
+                    } else {
+                        Log.i(this, "makeRoomForOutgoingEmergencyCall: Request to disconnect "
+                                + "ringing call failed. Aborting attempt to hold live call.");
+                        return CompletableFuture.completedFuture(false);
+                    }
+                }, new LoggedHandlerExecutor(mHandler, "CSC.mRFOEC",
+                        mCallsManager.getLock()));
+            } else {
+                return liveCall.hold(holdReason);
+            }
+        }
+
+        // The live call cannot be held so we're out of luck here.  There's no room.
+        emergencyCall.setStartFailCause(CallFailureCause.CANNOT_HOLD_CALL);
+        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) {
+        // 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);
+        }
+
+        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);
+        }
+
+        // First thing, for managed calls, if we are trying to make a call with the same phone
+        // account as the live call, then allow it so that the connection service can make its own
+        // decision about how to handle the new call relative to the current one.
+        // Note: This behavior is primarily in place because Telephony historically manages the
+        // state of the calls it tracks by itself, holding and unholding as needed.  Self-managed
+        // calls, even though from the same package are normally held/unheld automatically by
+        // Telecom.  Calls within a single ConnectionService get held/unheld automatically during
+        // "swap" operations by CallsManager#holdActiveCallForNewCall.  There is, however, a quirk
+        // in that if an app declares TWO different ConnectionServices, holdActiveCallForNewCall
+        // would not work correctly because focus switches between ConnectionServices, yet we
+        // tended to assume that if the calls are from the same package that the hold/unhold should
+        // be done by the app.  That was a bad assumption as it meant that we could have two active
+        // calls.
+        // TODO(b/280826075): We need to come back and revisit all this logic in a holistic manner.
+        if (PhoneAccountHandle.areFromSamePackage(liveCallPhoneAccount,
+                call.getTargetPhoneAccount())
+                && !call.isSelfManaged()
+                && !liveCall.isSelfManaged()) {
+            Log.i(this, "makeRoomForOutgoingCall: managed phoneAccount matches");
+            call.getAnalytics().setCallIsAdditional(true);
+            liveCall.getAnalytics().setCallIsInterrupted(true);
+            return CompletableFuture.completedFuture(true);
+        } else 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.
+        call.setStartFailCause(CallFailureCause.CANNOT_HOLD_CALL);
+        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(Call call1, Call call2) {
+        boolean areCallsFromSameSource = CallsManager.areFromSameSource(call1, call2);
+        if (!areCallsFromSameSource) {
+            setProcessingCallSequencing(true);
+        }
+    }
+
+    public boolean isProcessingCallSequencing() {
+        return mProcessingCallSequencing;
+    }
+
+    public Handler getHandler() {
+        return mHandler;
     }
 }
diff --git a/src/com/android/server/telecom/callsequencing/CallsManagerCallSequencingAdapter.java b/src/com/android/server/telecom/callsequencing/CallsManagerCallSequencingAdapter.java
index 8410c54..df0837d 100644
--- a/src/com/android/server/telecom/callsequencing/CallsManagerCallSequencingAdapter.java
+++ b/src/com/android/server/telecom/callsequencing/CallsManagerCallSequencingAdapter.java
@@ -16,8 +16,18 @@
 
 package com.android.server.telecom.callsequencing;
 
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.OutcomeReceiver;
+import android.telecom.CallAttributes;
+import android.telecom.CallException;
+import android.telecom.Log;
+
 import com.android.server.telecom.Call;
 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 java.util.concurrent.CompletableFuture;
 
@@ -29,16 +39,47 @@
 
     private final CallsManager mCallsManager;
     private final CallSequencingController mSequencingController;
+    private final Handler mHandler;
+    private final FeatureFlags mFeatureFlags;
     private final boolean mIsCallSequencingEnabled;
 
     public CallsManagerCallSequencingAdapter(CallsManager callsManager,
             CallSequencingController sequencingController,
-            boolean isCallSequencingEnabled) {
+            FeatureFlags featureFlags) {
         mCallsManager = callsManager;
         mSequencingController = sequencingController;
-        mIsCallSequencingEnabled = isCallSequencingEnabled;
+        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.
+     * @param videoState The video state configuration associated with the call.
+     */
     public void answerCall(Call incomingCall, int videoState) {
         if (mIsCallSequencingEnabled && !incomingCall.isTransactionalCall()) {
             mSequencingController.answerCall(incomingCall, videoState);
@@ -47,6 +88,11 @@
         }
     }
 
+    /**
+     * Conditionally attempt to unhold the provided call depending on whether call sequencing
+     * (mIsCallSequencingEnabled) is enabled.
+     * @param call The call to unhold.
+     */
     public void unholdCall(Call call) {
         if (mIsCallSequencingEnabled) {
             mSequencingController.unholdCall(call);
@@ -55,34 +101,111 @@
         }
     }
 
+    /**
+     * Conditionally attempt to hold the provided call depending on whether call sequencing
+     * (mIsCallSequencingEnabled) is enabled.
+     * @param call The call to hold.
+     */
     public void holdCall(Call call) {
         // Sequencing already taken care of for CSW/TSW in Call class.
-        call.hold();
+        CompletableFuture<Boolean> holdFuture = call.hold();
+        if (mIsCallSequencingEnabled) {
+            logFutureResultTransaction(holdFuture, "holdCall", "CMCSA.hC",
+                    "hold call transaction succeeded.", "hold call transaction failed.");
+        }
     }
 
-    public void unholdCallForRemoval(Call removedCall,
-            boolean isLocallyDisconnecting) {
-        // Todo: confirm verification of disconnect logic
-        // Sequencing already taken care of for CSW/TSW in Call class.
-        mCallsManager.maybeMoveHeldCallToForeground(removedCall, isLocallyDisconnecting);
+    /**
+     * Conditionally disconnect the provided call depending on whether call sequencing
+     * (mIsCallSequencingEnabled) is enabled. The sequencing functionality ensures that we wait for
+     * the call to be disconnected as signalled by CSW/TSW as to ensure that subsequent call
+     * operations don't overlap with this one.
+     * @param call The call to disconnect.
+     */
+    public void disconnectCall(Call call) {
+        int previousState = call.getState();
+        if (mIsCallSequencingEnabled) {
+            mSequencingController.disconnectCall(call, previousState);
+        } else {
+            mCallsManager.disconnectCallOld(call, previousState);
+        }
     }
 
+    /**
+     * Conditionally make room for the outgoing call depending on whether call sequencing
+     * (mIsCallSequencingEnabled) is enabled.
+     * @param isEmergency Indicator of whether the call is an emergency call.
+     * @param call The call to potentially make room for.
+     * @return {@link CompletableFuture} which will contain the result of the transaction if room
+     *         was able to made for the call.
+     */
     public CompletableFuture<Boolean> makeRoomForOutgoingCall(boolean isEmergency, Call call) {
         if (mIsCallSequencingEnabled) {
             return mSequencingController.makeRoomForOutgoingCall(isEmergency, call);
         } else {
             return isEmergency
                     ? CompletableFuture.completedFuture(
-                            makeRoomForOutgoingEmergencyCallFlagOff(call))
-                    : CompletableFuture.completedFuture(makeRoomForOutgoingCallFlagOff(call));
+                            mCallsManager.makeRoomForOutgoingEmergencyCall(call))
+                    : CompletableFuture.completedFuture(
+                            mCallsManager.makeRoomForOutgoingCall(call));
         }
     }
 
-    private boolean makeRoomForOutgoingCallFlagOff(Call call) {
-        return mCallsManager.makeRoomForOutgoingCall(call);
+    /**
+     * Attempts to mark the self-managed call as active by first holding the active call and then
+     * requesting call focus for the self-managed call.
+     * @param call The self-managed call to set active
+     */
+    public void markCallAsActiveSelfManagedCall(Call call) {
+        if (mIsCallSequencingEnabled) {
+            mSequencingController.handleSetSelfManagedCallActive(call);
+        } else {
+            mCallsManager.holdActiveCallForNewCall(call);
+            mCallsManager.requestActionSetActiveCall(call,
+                    "active set explicitly for self-managed");
+        }
     }
 
-    private boolean makeRoomForOutgoingEmergencyCallFlagOff(Call call) {
-        return mCallsManager.makeRoomForOutgoingEmergencyCall(call);
+    /**
+     * Attempts to hold the active call for transactional call cases with call sequencing support
+     * if mIsCallSequencingEnabled is true.
+     * @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.
+     */
+    public void transactionHoldPotentialActiveCallForNewCall(Call newCall,
+            Call activeCall, OutcomeReceiver<Boolean, CallException> callback) {
+        if (mIsCallSequencingEnabled) {
+            mSequencingController.transactionHoldPotentialActiveCallForNewCallSequencing(
+                    newCall, callback);
+        } else {
+            mCallsManager.transactionHoldPotentialActiveCallForNewCallOld(newCall,
+                    activeCall, callback);
+        }
+    }
+
+    /**
+     * 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) -> {
+            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 Handler getHandler() {
+        return mHandler;
     }
 }
diff --git a/src/com/android/server/telecom/callsequencing/TransactionManager.java b/src/com/android/server/telecom/callsequencing/TransactionManager.java
index a3b3828..2a6431b 100644
--- a/src/com/android/server/telecom/callsequencing/TransactionManager.java
+++ b/src/com/android/server/telecom/callsequencing/TransactionManager.java
@@ -169,14 +169,6 @@
         }
     }
 
-    public void setProcessingCallSequencing(boolean processingCallSequencing) {
-        mProcessingCallSequencing = processingCallSequencing;
-    }
-
-    public boolean isProcessingCallSequencing() {
-        return mProcessingCallSequencing;
-    }
-
     /**
      * Called when the dumpsys is created for telecom to capture the current state.
      */
diff --git a/src/com/android/server/telecom/callsequencing/TransactionalCallSequencingAdapter.java b/src/com/android/server/telecom/callsequencing/TransactionalCallSequencingAdapter.java
index 7c8bbe4..570c2cc 100644
--- a/src/com/android/server/telecom/callsequencing/TransactionalCallSequencingAdapter.java
+++ b/src/com/android/server/telecom/callsequencing/TransactionalCallSequencingAdapter.java
@@ -40,14 +40,13 @@
 public class TransactionalCallSequencingAdapter {
     private final TransactionManager mTransactionManager;
     private final CallsManager mCallsManager;
-//    private final boolean mIsCallSequencingEnabled;
+    private final boolean mIsCallSequencingEnabled;
 
     public TransactionalCallSequencingAdapter(TransactionManager transactionManager,
             CallsManager callsManager, boolean isCallSequencingEnabled) {
         mTransactionManager = transactionManager;
         mCallsManager = callsManager;
-        // TODO implement call sequencing changes
-//        mIsCallSequencingEnabled = isCallSequencingEnabled;
+        mIsCallSequencingEnabled = isCallSequencingEnabled;
     }
 
     /**
@@ -55,7 +54,13 @@
      */
     public void setActive(Call call,
             OutcomeReceiver<CallTransactionResult, CallException> receiver) {
-        setActiveFlagOff(call, receiver);
+        if (mIsCallSequencingEnabled) {
+            createSetActiveTransactionSequencing(call, true /* callControlRequest */, null,
+                    receiver, receiver);
+        } else {
+            mTransactionManager.addTransaction(createSetActiveTransactions(call,
+                    true /* callControlRequest */), receiver);
+        }
     }
 
     /**
@@ -63,7 +68,18 @@
      */
     public void setAnswered(Call call, int newVideoState,
             OutcomeReceiver<CallTransactionResult, CallException> receiver) {
-        setAnsweredFlagOff(call, newVideoState, receiver);
+        boolean isCallControlRequest = true;
+        OutcomeReceiver<CallTransactionResult, CallException> receiverForTransaction =
+                getSetAnswerReceiver(call, null /* foregroundCallBeforeSwap */,
+                        false /* wasForegroundActive */, newVideoState, receiver,
+                        isCallControlRequest);
+        if (mIsCallSequencingEnabled) {
+            createSetActiveTransactionSequencing(call, isCallControlRequest, null,
+                    receiver, receiverForTransaction /* receiverForTransaction */);
+        } else {
+            mTransactionManager.addTransaction(createSetActiveTransactions(call,
+                    isCallControlRequest), receiverForTransaction);
+        }
     }
 
     /**
@@ -71,7 +87,8 @@
      */
     public void setDisconnected(Call call, DisconnectCause dc,
             OutcomeReceiver<CallTransactionResult, CallException> receiver) {
-        setDisconnectedFlagOff(call, dc, receiver);
+        mTransactionManager.addTransaction(
+                new EndCallTransaction(mCallsManager, dc, call), receiver);
     }
 
     /**
@@ -79,7 +96,7 @@
      */
     public void setInactive(Call call,
             OutcomeReceiver<CallTransactionResult, CallException> receiver) {
-        setInactiveFlagOff(call, receiver);
+        mTransactionManager.addTransaction(new HoldCallTransaction(mCallsManager,call), receiver);
     }
 
     /**
@@ -89,16 +106,52 @@
     public CompletableFuture<Boolean> onSetActive(Call call,
             CallTransaction clientCbT,
             OutcomeReceiver<CallTransactionResult, CallException> receiver) {
-        return onSetActiveFlagOff(call, clientCbT, receiver);
+        // save CallsManager state before sending client state changes
+        Call foregroundCallBeforeSwap = mCallsManager.getForegroundCall();
+        boolean wasActive = foregroundCallBeforeSwap != null && foregroundCallBeforeSwap.isActive();
+        OutcomeReceiver<CallTransactionResult, CallException> receiverForTransaction =
+                getOnSetActiveReceiver(call, foregroundCallBeforeSwap, wasActive, receiver);
+
+        if (mIsCallSequencingEnabled) {
+            return createSetActiveTransactionSequencing(call, false /* callControlRequest */,
+                    clientCbT, receiver, receiverForTransaction);
+        } else {
+            SerialTransaction serialTransactions = createSetActiveTransactions(call,
+                    false /* callControlRequest */);
+            serialTransactions.appendTransaction(clientCbT);
+            // do CallsManager workload before asking client and
+            //   reset CallsManager state if client does NOT ack
+            return mTransactionManager.addTransaction(
+                    serialTransactions, receiverForTransaction);
+        }
     }
 
     /**
      * Server -> Client command to answer an incoming call, which if it fails, will trigger the
      * disconnect of the call and then reset the state of the other call back to what it was before.
      */
-    public void onSetAnswered(Call call, int videoState, CallTransaction clientCbT,
-            OutcomeReceiver<CallTransactionResult, CallException> receiver) {
-        onSetAnsweredFlagOff(call, videoState, clientCbT, receiver);
+    public CompletableFuture<Boolean> onSetAnswered(Call call, int videoState,
+            CallTransaction clientCbT, OutcomeReceiver<CallTransactionResult,
+            CallException> receiver) {
+        boolean isCallControlRequest = false;
+        // save CallsManager state before sending client state changes
+        Call foregroundCallBeforeSwap = mCallsManager.getForegroundCall();
+        boolean wasActive = foregroundCallBeforeSwap != null && foregroundCallBeforeSwap.isActive();
+        OutcomeReceiver<CallTransactionResult, CallException> receiverForTransaction =
+                getSetAnswerReceiver(call, foregroundCallBeforeSwap, wasActive,
+                        videoState, receiver, isCallControlRequest);
+
+        if (mIsCallSequencingEnabled) {
+            return createSetActiveTransactionSequencing(call, false /* callControlRequest */,
+                    clientCbT, receiver, receiverForTransaction);
+        } else {
+            SerialTransaction serialTransactions = createSetActiveTransactions(call,
+                    isCallControlRequest);
+            serialTransactions.appendTransaction(clientCbT);
+            // do CallsManager workload before asking client and
+            //   reset CallsManager state if client does NOT ack
+            return mTransactionManager.addTransaction(serialTransactions, receiverForTransaction);
+        }
     }
 
     /**
@@ -107,7 +160,19 @@
     public CompletableFuture<Boolean> onSetInactive(Call call,
             CallTransaction clientCbT,
             OutcomeReceiver<CallTransactionResult, CallException> receiver) {
-        return onSetInactiveFlagOff(call, clientCbT, receiver);
+        return mTransactionManager.addTransaction(clientCbT,
+                new OutcomeReceiver<>() {
+                    @Override
+                    public void onResult(CallTransactionResult callTransactionResult) {
+                        mCallsManager.markCallAsOnHold(call);
+                        receiver.onResult(callTransactionResult);
+                    }
+
+                    @Override
+                    public void onError(CallException error) {
+                        receiver.onError(error);
+                    }
+                });
     }
 
     /**
@@ -116,7 +181,20 @@
     public CompletableFuture<Boolean> onSetDisconnected(Call call,
             DisconnectCause dc, CallTransaction clientCbT, OutcomeReceiver<CallTransactionResult,
             CallException> receiver) {
-        return onSetDisconnectedFlagOff(call, dc, clientCbT, receiver);
+        return mTransactionManager.addTransaction(clientCbT,
+                new OutcomeReceiver<>() {
+                    @Override
+                    public void onResult(CallTransactionResult result) {
+                        removeCallFromCallsManager(call, dc);
+                        receiver.onResult(result);
+                    }
+
+                    @Override
+                    public void onError(CallException exception) {
+                        removeCallFromCallsManager(call, dc);
+                        receiver.onError(exception);
+                    }
+                });
     }
 
     /**
@@ -126,146 +204,6 @@
         cleanupFlagOff(calls);
     }
 
-    private void setActiveFlagOff(Call call,
-            OutcomeReceiver<CallTransactionResult, CallException> receiver) {
-        CompletableFuture<Boolean> transactionResult = mTransactionManager
-                .addTransaction(createSetActiveTransactions(call,
-                true /* callControlRequest */), receiver);
-    }
-
-    private void setAnsweredFlagOff(Call call, int newVideoState,
-            OutcomeReceiver<CallTransactionResult, CallException> receiver) {
-        CompletableFuture<Boolean> transactionResult = mTransactionManager
-                .addTransaction(createSetActiveTransactions(call,
-                                true /* callControlRequest */),
-                new OutcomeReceiver<>() {
-                    @Override
-                    public void onResult(CallTransactionResult callTransactionResult) {
-                        call.setVideoState(newVideoState);
-                        receiver.onResult(callTransactionResult);
-                    }
-
-                    @Override
-                    public void onError(CallException error) {
-                        receiver.onError(error);
-                    }
-                });
-    }
-
-    private void setDisconnectedFlagOff(Call call, DisconnectCause dc,
-            OutcomeReceiver<CallTransactionResult, CallException> receiver) {
-        CompletableFuture<Boolean> transactionResult = mTransactionManager
-                .addTransaction(new EndCallTransaction(mCallsManager,
-                        dc, call), receiver);
-    }
-
-    private void setInactiveFlagOff(Call call,
-            OutcomeReceiver<CallTransactionResult, CallException> receiver) {
-        CompletableFuture<Boolean> transactionResult = mTransactionManager
-                .addTransaction(new HoldCallTransaction(mCallsManager,call), receiver);
-    }
-
-    private CompletableFuture<Boolean> onSetActiveFlagOff(Call call,
-            CallTransaction clientCbT,
-            OutcomeReceiver<CallTransactionResult, CallException> receiver) {
-        // save CallsManager state before sending client state changes
-        Call foregroundCallBeforeSwap = mCallsManager.getForegroundCall();
-        boolean wasActive = foregroundCallBeforeSwap != null && foregroundCallBeforeSwap.isActive();
-        SerialTransaction serialTransactions = createSetActiveTransactions(call,
-                false /* callControlRequest */);
-        serialTransactions.appendTransaction(clientCbT);
-        // do CallsManager workload before asking client and
-        //   reset CallsManager state if client does NOT ack
-        return mTransactionManager.addTransaction(
-                serialTransactions,
-                new OutcomeReceiver<>() {
-                    @Override
-                    public void onResult(CallTransactionResult result) {
-                        receiver.onResult(result);
-                    }
-
-                    @Override
-                    public void onError(CallException exception) {
-                        mCallsManager.markCallAsOnHold(call);
-                        maybeResetForegroundCall(foregroundCallBeforeSwap, wasActive);
-                        receiver.onError(exception);
-                    }
-                });
-    }
-
-    private void onSetAnsweredFlagOff(Call call, int videoState, CallTransaction clientCbT,
-            OutcomeReceiver<CallTransactionResult, CallException> receiver) {
-        // save CallsManager state before sending client state changes
-        Call foregroundCallBeforeSwap = mCallsManager.getForegroundCall();
-        boolean wasActive = foregroundCallBeforeSwap != null && foregroundCallBeforeSwap.isActive();
-        SerialTransaction serialTransactions = createSetActiveTransactions(call,
-                false /* callControlRequest */);
-        serialTransactions.appendTransaction(clientCbT);
-        // do CallsManager workload before asking client and
-        //   reset CallsManager state if client does NOT ack
-        CompletableFuture<Boolean> transactionResult = mTransactionManager
-                .addTransaction(serialTransactions,
-                new OutcomeReceiver<>() {
-                    @Override
-                    public void onResult(CallTransactionResult result) {
-                        call.setVideoState(videoState);
-                        receiver.onResult(result);
-                    }
-
-                    @Override
-                    public void onError(CallException exception) {
-                        // This also sends the signal to untrack from TSW and the client_TSW
-                        removeCallFromCallsManager(call,
-                                new DisconnectCause(DisconnectCause.REJECTED,
-                                        "client rejected to answer the call;"
-                                                + " force disconnecting"));
-                        maybeResetForegroundCall(foregroundCallBeforeSwap, wasActive);
-                        receiver.onError(exception);
-                    }
-                });
-    }
-
-    private CompletableFuture<Boolean> onSetInactiveFlagOff(Call call,
-            CallTransaction clientCbT,
-            OutcomeReceiver<CallTransactionResult, CallException> receiver) {
-        return mTransactionManager.addTransaction(clientCbT,
-                new OutcomeReceiver<>() {
-                    @Override
-                    public void onResult(CallTransactionResult callTransactionResult) {
-                        mCallsManager.markCallAsOnHold(call);
-                        receiver.onResult(callTransactionResult);
-                    }
-
-                    @Override
-                    public void onError(CallException error) {
-                        receiver.onError(error);
-                    }
-                });
-    }
-
-    /**
-     * Server -> Client command to disconnect the call
-     */
-    private CompletableFuture<Boolean> onSetDisconnectedFlagOff(Call call,
-            DisconnectCause dc, CallTransaction clientCbT,
-            OutcomeReceiver<CallTransactionResult, CallException> receiver) {
-        return mTransactionManager.addTransaction(clientCbT,
-                new OutcomeReceiver<>() {
-                    @Override
-                    public void onResult(CallTransactionResult result) {
-                        removeCallFromCallsManager(call, dc);
-                        receiver.onResult(result);
-                    }
-
-                    @Override
-                    public void onError(CallException exception) {
-                        removeCallFromCallsManager(call, dc);
-                        receiver.onError(exception);
-                    }
-                }
-        );
-    }
-
     private SerialTransaction createSetActiveTransactions(Call call, boolean isCallControlRequest) {
         // create list for multiple transactions
         List<CallTransaction> transactions = new ArrayList<>();
@@ -279,6 +217,48 @@
         return new SerialTransaction(transactions, mCallsManager.getLock());
     }
 
+    /**
+     * This code path is invoked when mIsCallSequencingEnabled is true. We will first try to hold
+     * the active call before adding the transactions to request call focus for the new call as well
+     * as verify the client ack for the transaction (if applicable). If the hold transaction
+     * succeeds, we will continue processing the rest of the transactions via a SerialTransaction.
+     */
+    private CompletableFuture<Boolean> createSetActiveTransactionSequencing(
+            Call call, boolean isCallControlRequest, CallTransaction clientCbT,
+            OutcomeReceiver<CallTransactionResult, CallException> receiver,
+            OutcomeReceiver<CallTransactionResult, CallException> receiverForTransaction) {
+        final CompletableFuture<Boolean>[] createSetActiveFuture =
+                new CompletableFuture[]{new CompletableFuture<>()};
+        OutcomeReceiver<Boolean, CallException> maybePerformHoldCallback = new OutcomeReceiver<>() {
+            @Override
+            public void onResult(Boolean result) {
+                // Transaction not yet completed. Still need to request focus for active call and
+                // process client callback transaction if applicable.
+                // create list for multiple transactions
+                List<CallTransaction> transactions = new ArrayList<>();
+                // And request a new focus call update
+                transactions.add(new RequestNewActiveCallTransaction(mCallsManager, call));
+                if (clientCbT != null){
+                    transactions.add(clientCbT);
+                }
+                SerialTransaction serialTransactions = new SerialTransaction(
+                        transactions, mCallsManager.getLock());
+                createSetActiveFuture[0] = mTransactionManager.addTransaction(serialTransactions,
+                        receiverForTransaction);
+            }
+
+            @Override
+            public void onError(CallException exception) {
+                createSetActiveFuture[0] = CompletableFuture.completedFuture(false);
+                receiver.onError(exception);
+            }
+        };
+
+        mCallsManager.transactionHoldPotentialActiveCallForNewCall(call,
+                isCallControlRequest, maybePerformHoldCallback);
+        return createSetActiveFuture[0];
+    }
+
     private void removeCallFromCallsManager(Call call, DisconnectCause cause) {
         if (cause.getCode() != DisconnectCause.REJECTED) {
             mCallsManager.markCallAsDisconnected(call, cause);
@@ -301,4 +281,49 @@
             mCallsManager.removeCall(call); // This will clear mTrackedCalls && ClientTWS
         }
     }
+
+    private OutcomeReceiver<CallTransactionResult, CallException> getOnSetActiveReceiver(
+            Call call, Call foregroundCallBeforeSwap, boolean wasForegroundActive,
+            OutcomeReceiver<CallTransactionResult, CallException> receiver) {
+        return new OutcomeReceiver<>() {
+            @Override
+            public void onResult(CallTransactionResult result) {
+                receiver.onResult(result);
+            }
+
+            @Override
+            public void onError(CallException exception) {
+                mCallsManager.markCallAsOnHold(call);
+                maybeResetForegroundCall(foregroundCallBeforeSwap, wasForegroundActive);
+                receiver.onError(exception);
+            }
+        };
+    }
+
+    private OutcomeReceiver<CallTransactionResult, CallException> getSetAnswerReceiver(
+            Call call, Call foregroundCallBeforeSwap, boolean wasForegroundActive, int videoState,
+            OutcomeReceiver<CallTransactionResult, CallException> receiver,
+            boolean isCallControlRequest) {
+        return new OutcomeReceiver<>() {
+            @Override
+            public void onResult(CallTransactionResult result) {
+                call.setVideoState(videoState);
+                receiver.onResult(result);
+            }
+
+            @Override
+            public void onError(CallException exception) {
+                if (!isCallControlRequest) {
+                    // This also sends the signal to untrack from TSW and the
+                    // client_TSW
+                    removeCallFromCallsManager(call,
+                            new DisconnectCause(DisconnectCause.REJECTED,
+                                    "client rejected to answer the call;"
+                                            + " force disconnecting"));
+                    maybeResetForegroundCall(foregroundCallBeforeSwap, wasForegroundActive);
+                }
+                receiver.onError(exception);
+            }
+        };
+    }
 }
diff --git a/src/com/android/server/telecom/callsequencing/voip/OutgoingCallTransaction.java b/src/com/android/server/telecom/callsequencing/voip/OutgoingCallTransaction.java
index 572de55..b221579 100644
--- a/src/com/android/server/telecom/callsequencing/voip/OutgoingCallTransaction.java
+++ b/src/com/android/server/telecom/callsequencing/voip/OutgoingCallTransaction.java
@@ -91,7 +91,7 @@
             CompletableFuture<Call> callFuture =
                     mCallsManager.startOutgoingCall(mCallAttributes.getAddress(),
                             mCallAttributes.getPhoneAccountHandle(),
-                            generateExtras(mCallAttributes),
+                            generateExtras(mCallId, mExtras, mCallAttributes, mFeatureFlags),
                             mCallAttributes.getPhoneAccountHandle().getUserHandle(),
                             intent,
                             mCallingPackage);
@@ -102,35 +102,11 @@
                                 CODE_CALL_NOT_PERMITTED_AT_PRESENT_TIME,
                                 "incoming call not permitted at the current time"));
             }
-            CompletionStage<CallTransactionResult> result = callFuture.thenComposeAsync(
-                    (call) -> {
 
-                        Log.d(TAG, "processTransaction: completing future");
-
-                        if (call == null) {
-                            Log.d(TAG, "processTransaction: call is null");
-                            return CompletableFuture.completedFuture(
-                                    new CallTransactionResult(
-                                            CODE_CALL_NOT_PERMITTED_AT_PRESENT_TIME,
-                                            "call could not be created at this time"));
-                        } else {
-                            Log.d(TAG, "processTransaction: call done. id=" + call.getId());
-                        }
-
-                        if (mFeatureFlags.disconnectSelfManagedStuckStartupCalls()) {
-                            // set to dialing so the CallAnomalyWatchdog gives the VoIP calls 1
-                            // minute to timeout rather than 5 seconds.
-                            mCallsManager.markCallAsDialing(call);
-                        }
-
-                        return CompletableFuture.completedFuture(
-                                new CallTransactionResult(
-                                        CallTransactionResult.RESULT_SUCCEED,
-                                        call, null, true));
-                    }
+            return callFuture.thenComposeAsync(
+                    (call) -> processOutgoingCallTransactionHelper(call, TAG,
+                            mCallsManager, mFeatureFlags)
                     , new LoggedHandlerExecutor(mHandler, "OCT.pT", null));
-
-            return result;
         } else {
             return CompletableFuture.completedFuture(
                     new CallTransactionResult(
@@ -141,20 +117,47 @@
     }
 
     @VisibleForTesting
-    public Bundle generateExtras(CallAttributes callAttributes) {
-        mExtras.setDefusable(true);
-        mExtras.putString(TelecomManager.TRANSACTION_CALL_ID_KEY, mCallId);
-        mExtras.putInt(CALL_CAPABILITIES_KEY, callAttributes.getCallCapabilities());
-        if (mFeatureFlags.transactionalVideoState()) {
+    public static Bundle generateExtras(String callId, Bundle extras,
+            CallAttributes callAttributes, FeatureFlags featureFlags) {
+        extras.setDefusable(true);
+        extras.putString(TelecomManager.TRANSACTION_CALL_ID_KEY, callId);
+        extras.putInt(CALL_CAPABILITIES_KEY, callAttributes.getCallCapabilities());
+        if (featureFlags.transactionalVideoState()) {
             // Transactional calls need to remap the CallAttributes video state to the existing
             // VideoProfile for consistency.
-            mExtras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
+            extras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
                     TransactionalVideoStateToVideoProfileState(callAttributes.getCallType()));
         } else {
-            mExtras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
+            extras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
                     callAttributes.getCallType());
         }
-        mExtras.putCharSequence(DISPLAY_NAME_KEY, callAttributes.getDisplayName());
-        return mExtras;
+        extras.putCharSequence(DISPLAY_NAME_KEY, callAttributes.getDisplayName());
+        return extras;
+    }
+
+    public static CompletableFuture<CallTransactionResult> processOutgoingCallTransactionHelper(
+            Call call, String tag, CallsManager callsManager, FeatureFlags featureFlags) {
+        Log.d(tag, "processTransaction: completing future");
+
+        if (call == null) {
+            Log.d(tag, "processTransaction: call is null");
+            return CompletableFuture.completedFuture(
+                    new CallTransactionResult(
+                            CODE_CALL_NOT_PERMITTED_AT_PRESENT_TIME,
+                            "call could not be created at this time"));
+        } else {
+            Log.d(tag, "processTransaction: call done. id=" + call.getId());
+        }
+
+        if (featureFlags.disconnectSelfManagedStuckStartupCalls()) {
+            // set to dialing so the CallAnomalyWatchdog gives the VoIP calls 1
+            // minute to timeout rather than 5 seconds.
+            callsManager.markCallAsDialing(call);
+        }
+
+        return CompletableFuture.completedFuture(
+                new CallTransactionResult(
+                        CallTransactionResult.RESULT_SUCCEED,
+                        call, null, true));
     }
 }
diff --git a/src/com/android/server/telecom/callsequencing/voip/OutgoingCallTransactionSequencing.java b/src/com/android/server/telecom/callsequencing/voip/OutgoingCallTransactionSequencing.java
new file mode 100644
index 0000000..c38b55d
--- /dev/null
+++ b/src/com/android/server/telecom/callsequencing/voip/OutgoingCallTransactionSequencing.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.callsequencing.voip;
+
+import static android.telecom.CallException.CODE_CALL_NOT_PERMITTED_AT_PRESENT_TIME;
+
+import android.util.Log;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.LoggedHandlerExecutor;
+import com.android.server.telecom.callsequencing.CallTransaction;
+import com.android.server.telecom.callsequencing.CallTransactionResult;
+import com.android.server.telecom.flags.FeatureFlags;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+
+public class OutgoingCallTransactionSequencing extends CallTransaction {
+
+    private static final String TAG = OutgoingCallTransactionSequencing.class.getSimpleName();
+    private final CompletableFuture<Call> mCallFuture;
+    private final CallsManager mCallsManager;
+    private FeatureFlags mFeatureFlags;
+
+    public OutgoingCallTransactionSequencing(CallsManager callsManager,
+            CompletableFuture<Call> callFuture, FeatureFlags featureFlags) {
+        super(callsManager.getLock());
+        mCallsManager = callsManager;
+        mCallFuture = callFuture;
+        mFeatureFlags = featureFlags;
+    }
+
+    @Override
+    public CompletionStage<CallTransactionResult> processTransaction(Void v) {
+        Log.d(TAG, "processTransaction");
+        if (mCallFuture == null) {
+            return CompletableFuture.completedFuture(
+                    new CallTransactionResult(
+                            CODE_CALL_NOT_PERMITTED_AT_PRESENT_TIME,
+                            "outgoing call not permitted at the current time"));
+        }
+
+        return mCallFuture.thenComposeAsync(
+                (call) -> OutgoingCallTransaction.processOutgoingCallTransactionHelper(call, TAG,
+                        mCallsManager, mFeatureFlags)
+                , new LoggedHandlerExecutor(mHandler, "OCT.pT", null));
+    }
+}
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index 2d8c78e..4db3e14 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -81,10 +81,11 @@
         Log.d(this, "onBind");
         return new ITelecomLoader.Stub() {
             @Override
-            public ITelecomService createTelecomService(IInternalServiceRetriever retriever) {
+            public ITelecomService createTelecomService(IInternalServiceRetriever retriever,
+                    String sysUiPackageName) {
                 InternalServiceRetrieverAdapter adapter =
                         new InternalServiceRetrieverAdapter(retriever);
-                initializeTelecomSystem(TelecomService.this, adapter);
+                initializeTelecomSystem(TelecomService.this, adapter, sysUiPackageName);
                 synchronized (getTelecomSystem().getLock()) {
                     return getTelecomSystem().getTelecomServiceImpl().getBinder();
                 }
@@ -103,7 +104,7 @@
      * @param context
      */
     static void initializeTelecomSystem(Context context,
-            InternalServiceRetrieverAdapter internalServiceRetriever) {
+            InternalServiceRetrieverAdapter internalServiceRetriever, String sysUiPackageName) {
         if (TelecomSystem.getInstance() == null) {
             FeatureFlags featureFlags = new FeatureFlagsImpl();
             NotificationChannelManager notificationChannelManager =
@@ -204,6 +205,7 @@
                                     (RoleManager) context.getSystemService(Context.ROLE_SERVICE)),
                             new ContactsAsyncHelper.Factory(),
                             internalServiceRetriever.getDeviceIdleController(),
+                            sysUiPackageName,
                             new Ringer.AccessibilityManagerAdapter() {
                                 @Override
                                 public boolean startFlashNotificationSequence(
diff --git a/src/com/android/server/telecom/metrics/ApiStats.java b/src/com/android/server/telecom/metrics/ApiStats.java
index f0b5dc7..4b23e47 100644
--- a/src/com/android/server/telecom/metrics/ApiStats.java
+++ b/src/com/android/server/telecom/metrics/ApiStats.java
@@ -191,6 +191,8 @@
             Arrays.stream(mPulledAtoms.telecomApiStats).forEach(v -> data.add(
                     TelecomStatsLog.buildStatsEvent(getTag(),
                             v.getApiName(), v.getUid(), v.getApiResult(), v.getCount())));
+            mApiStatsMap.clear();
+            onAggregate();
             return StatsManager.PULL_SUCCESS;
         } else {
             return StatsManager.PULL_SKIP;
diff --git a/src/com/android/server/telecom/metrics/AudioRouteStats.java b/src/com/android/server/telecom/metrics/AudioRouteStats.java
index 21624f1..4611b22 100644
--- a/src/com/android/server/telecom/metrics/AudioRouteStats.java
+++ b/src/com/android/server/telecom/metrics/AudioRouteStats.java
@@ -99,6 +99,8 @@
                     TelecomStatsLog.buildStatsEvent(getTag(),
                             v.getCallAudioRouteSource(), v.getCallAudioRouteDest(),
                             v.getSuccess(), v.getRevert(), v.getCount(), v.getAverageLatencyMs())));
+            mAudioRouteStatsMap.clear();
+            onAggregate();
             return StatsManager.PULL_SUCCESS;
         } else {
             return StatsManager.PULL_SKIP;
diff --git a/src/com/android/server/telecom/metrics/CallStats.java b/src/com/android/server/telecom/metrics/CallStats.java
index 7ebeba6..8bdeffb 100644
--- a/src/com/android/server/telecom/metrics/CallStats.java
+++ b/src/com/android/server/telecom/metrics/CallStats.java
@@ -81,6 +81,8 @@
                             v.getCallDirection(), v.getExternalCall(), v.getEmergencyCall(),
                             v.getMultipleAudioAvailable(), v.getAccountType(), v.getUid(),
                             v.getCount(), v.getAverageDurationMs())));
+            mCallStatsMap.clear();
+            onAggregate();
             return StatsManager.PULL_SUCCESS;
         } else {
             return StatsManager.PULL_SKIP;
diff --git a/src/com/android/server/telecom/metrics/ErrorStats.java b/src/com/android/server/telecom/metrics/ErrorStats.java
index f70f6d8..f334710 100644
--- a/src/com/android/server/telecom/metrics/ErrorStats.java
+++ b/src/com/android/server/telecom/metrics/ErrorStats.java
@@ -140,6 +140,8 @@
             Arrays.stream(mPulledAtoms.telecomErrorStats).forEach(v -> data.add(
                     TelecomStatsLog.buildStatsEvent(getTag(),
                             v.getSubmodule(), v.getError(), v.getCount())));
+            mErrorStatsMap.clear();
+            onAggregate();
             return StatsManager.PULL_SUCCESS;
         } else {
             return StatsManager.PULL_SKIP;
diff --git a/src/com/android/server/telecom/metrics/TelecomMetricsController.java b/src/com/android/server/telecom/metrics/TelecomMetricsController.java
index df735c0..c642303 100644
--- a/src/com/android/server/telecom/metrics/TelecomMetricsController.java
+++ b/src/com/android/server/telecom/metrics/TelecomMetricsController.java
@@ -24,6 +24,7 @@
 import android.annotation.NonNull;
 import android.app.StatsManager;
 import android.content.Context;
+import android.os.Binder;
 import android.os.HandlerThread;
 import android.telecom.Log;
 import android.util.StatsEvent;
@@ -73,8 +74,13 @@
     public ApiStats getApiStats() {
         ApiStats stats = (ApiStats) mStats.get(TELECOM_API_STATS);
         if (stats == null) {
-            stats = new ApiStats(mContext, mHandlerThread.getLooper());
-            registerAtom(stats.getTag(), stats);
+            long token = Binder.clearCallingIdentity();
+            try {
+                stats = new ApiStats(mContext, mHandlerThread.getLooper());
+                registerAtom(stats.getTag(), stats);
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
         }
         return stats;
     }
diff --git a/src/com/android/server/telecom/ui/CallStreamingNotification.java b/src/com/android/server/telecom/ui/CallStreamingNotification.java
index 8414047..06da5e3 100644
--- a/src/com/android/server/telecom/ui/CallStreamingNotification.java
+++ b/src/com/android/server/telecom/ui/CallStreamingNotification.java
@@ -192,7 +192,7 @@
         // Use the caller name for the label if available, default to app name if none.
         if (TextUtils.isEmpty(callerName)) {
             // App did not provide a caller name, so default to app's name.
-            callerName = mAppLabelProxy.getAppLabel(appPackageName).toString();
+            callerName = mAppLabelProxy.getAppLabel(appPackageName, userHandle).toString();
         }
 
         // Action to hangup; this can use the default hangup action from the call style
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
index ac4a94e..9f97bbe 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
@@ -18,6 +18,9 @@
 
 import static android.media.AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
 
+import static com.android.server.telecom.CallAudioRouteAdapter.SWITCH_BASELINE_ROUTE;
+import static com.android.server.telecom.CallAudioRouteController.INCLUDE_BLUETOOTH_IN_BASELINE;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
@@ -44,11 +47,14 @@
 import android.media.AudioManager;
 import android.os.Bundle;
 import android.os.Parcel;
+import android.telecom.CallAudioState;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.telecom.AudioRoute;
 import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
 import com.android.server.telecom.CallAudioRouteAdapter;
+import com.android.server.telecom.CallAudioRouteController;
 import com.android.server.telecom.bluetooth.BluetoothDeviceManager;
 import com.android.server.telecom.bluetooth.BluetoothRouteManager;
 import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
@@ -64,7 +70,9 @@
 import static org.mockito.Mockito.reset;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.Executor;
 
 @RunWith(JUnit4.class)
@@ -79,6 +87,8 @@
     @Mock AudioManager mockAudioManager;
     @Mock AudioDeviceInfo mSpeakerInfo;
     @Mock Executor mExecutor;
+    @Mock CallAudioRouteController mCallAudioRouteController;
+    @Mock CallAudioState mCallAudioState;
 
     BluetoothDeviceManager mBluetoothDeviceManager;
     BluetoothProfile.ServiceListener serviceListenerUnderTest;
@@ -115,6 +125,7 @@
         mBluetoothDeviceManager = new BluetoothDeviceManager(mContext, mAdapter,
                 mCommunicationDeviceTracker, mFeatureFlags);
         mBluetoothDeviceManager.setBluetoothRouteManager(mRouteManager);
+        mBluetoothDeviceManager.setCallAudioRouteAdapter(mCallAudioRouteController);
         mCommunicationDeviceTracker.setBluetoothRouteManager(mRouteManager);
 
         mockAudioManager = mContext.getSystemService(AudioManager.class);
@@ -299,6 +310,38 @@
 
     @SmallTest
     @Test
+    public void testHandleAudioRefactoringServiceDisconnectedWhileBluetooth() {
+        when(mFeatureFlags.skipBaselineSwitchWhenRouteNotBluetooth()).thenReturn(true);
+        Map<AudioRoute, BluetoothDevice> btRoutes = new HashMap<>();
+        when(mCallAudioRouteController.getBluetoothRoutes()).thenReturn(btRoutes);
+        when(mCallAudioRouteController.getCurrentCallAudioState()).thenReturn(mCallAudioState);
+        when(mCallAudioState.getRoute()).thenReturn(CallAudioState.ROUTE_BLUETOOTH);
+
+        mBluetoothDeviceManager
+                .handleAudioRefactoringServiceDisconnected(BluetoothProfile.LE_AUDIO);
+
+        verify(mCallAudioRouteController).sendMessageWithSessionInfo(SWITCH_BASELINE_ROUTE,
+                INCLUDE_BLUETOOTH_IN_BASELINE, (String) null);
+    }
+
+    @SmallTest
+    @Test
+    public void testHandleAudioRefactoringServiceDisconnectedWhileSpeaker() {
+        when(mFeatureFlags.skipBaselineSwitchWhenRouteNotBluetooth()).thenReturn(true);
+        Map<AudioRoute, BluetoothDevice> btRoutes = new HashMap<>();
+        when(mCallAudioRouteController.getBluetoothRoutes()).thenReturn(btRoutes);
+        when(mCallAudioRouteController.getCurrentCallAudioState()).thenReturn(mCallAudioState);
+        when(mCallAudioState.getRoute()).thenReturn(CallAudioState.ROUTE_SPEAKER);
+
+        mBluetoothDeviceManager
+                .handleAudioRefactoringServiceDisconnected(BluetoothProfile.LE_AUDIO);
+
+        verify(mCallAudioRouteController, never()).sendMessageWithSessionInfo(SWITCH_BASELINE_ROUTE,
+                INCLUDE_BLUETOOTH_IN_BASELINE, (String) null);
+    }
+
+    @SmallTest
+    @Test
     public void testHeadsetServiceDisconnect() {
         receiverUnderTest.onReceive(mContext,
                 buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1,
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
index 7d614dc..e234d9d 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
@@ -57,6 +57,7 @@
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.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;
@@ -75,6 +76,7 @@
 import android.os.UserHandle;
 import android.telecom.CallAudioState;
 import android.telecom.VideoProfile;
+import android.util.Pair;
 
 import androidx.test.filters.SmallTest;
 
@@ -730,6 +732,14 @@
 
     @SmallTest
     @Test
+    public void testConnectDisconnectScoDuringCallNoClear() {
+        when(mFeatureFlags.onlyClearCommunicationDeviceOnInactive()).thenReturn(true);
+        verifyConnectBluetoothDevice(AudioRoute.TYPE_BLUETOOTH_SCO);
+        verifyDisconnectBluetoothDevice(AudioRoute.TYPE_BLUETOOTH_SCO);
+    }
+
+    @SmallTest
+    @Test
     public void testConnectAndDisconnectLeDeviceDuringCall() {
         when(mBluetoothLeAudio.getConnectedGroupLeadDevice(anyInt()))
                 .thenReturn(BLUETOOTH_DEVICE_1);
@@ -739,6 +749,16 @@
 
     @SmallTest
     @Test
+    public void testConnectAndDisconnectLeDeviceDuringCallNoClear() {
+        when(mFeatureFlags.onlyClearCommunicationDeviceOnInactive()).thenReturn(true);
+        when(mBluetoothLeAudio.getConnectedGroupLeadDevice(anyInt()))
+                .thenReturn(BLUETOOTH_DEVICE_1);
+        verifyConnectBluetoothDevice(AudioRoute.TYPE_BLUETOOTH_LE);
+        verifyDisconnectBluetoothDevice(AudioRoute.TYPE_BLUETOOTH_LE);
+    }
+
+    @SmallTest
+    @Test
     public void testConnectAndDisconnectHearingAidDuringCall() {
         verifyConnectBluetoothDevice(AudioRoute.TYPE_BLUETOOTH_HA);
         verifyDisconnectBluetoothDevice(AudioRoute.TYPE_BLUETOOTH_HA);
@@ -746,6 +766,15 @@
 
     @SmallTest
     @Test
+    public void testConnectAndDisconnectHearingAidDuringCallNoClear() {
+        when(mFeatureFlags.onlyClearCommunicationDeviceOnInactive()).thenReturn(true);
+        verifyConnectBluetoothDevice(AudioRoute.TYPE_BLUETOOTH_HA);
+        verifyDisconnectBluetoothDevice(AudioRoute.TYPE_BLUETOOTH_HA);
+    }
+
+
+    @SmallTest
+    @Test
     public void testSwitchBetweenLeAndScoDevices() {
         when(mBluetoothLeAudio.getConnectedGroupLeadDevice(anyInt()))
                 .thenReturn(BLUETOOTH_DEVICE_1);
@@ -1113,6 +1142,63 @@
 
     @Test
     @SmallTest
+    public void testRouteToWatchWhenCallAnsweredOnWatch_MultipleBtDevices() {
+        when(mFeatureFlags.resolveActiveBtRoutingAndBtTimingIssue()).thenReturn(true);
+        // Connect first BT device.
+        verifyConnectBluetoothDevice(AudioRoute.TYPE_BLUETOOTH_SCO);
+        // Connect another BT device.
+        String scoDeviceAddress = "00:00:00:00:00:03";
+        BluetoothDevice watchDevice =
+                BluetoothRouteManagerTest.makeBluetoothDevice(scoDeviceAddress);
+        when(mBluetoothRouteManager.isWatch(eq(watchDevice))).thenReturn(true);
+        BLUETOOTH_DEVICES.add(watchDevice);
+
+        mController.sendMessageWithSessionInfo(BT_DEVICE_ADDED, AudioRoute.TYPE_BLUETOOTH_SCO,
+                watchDevice);
+        CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER
+                | CallAudioState.ROUTE_BLUETOOTH, BLUETOOTH_DEVICE_1, BLUETOOTH_DEVICES);
+        verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+                any(CallAudioState.class), eq(expectedState));
+
+        // Signal that watch is now the active device. This is done in BluetoothStateReceiver and
+        // then BT_ACTIVE_DEVICE_PRESENT will be sent to the controller to be processed.
+        mController.updateActiveBluetoothDevice(
+                new Pair<>(AudioRoute.TYPE_BLUETOOTH_SCO, watchDevice.getAddress()));
+        // Emulate scenario with call answered on watch. Ensure at this point that audio was routed
+        // into watch
+        mController.sendMessageWithSessionInfo(SWITCH_FOCUS, ACTIVE_FOCUS, 0);
+        mController.sendMessageWithSessionInfo(BT_AUDIO_CONNECTED,
+                0, watchDevice);
+        mController.sendMessageWithSessionInfo(BT_AUDIO_DISCONNECTED,
+                0, BLUETOOTH_DEVICE_1);
+        expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER
+                        | CallAudioState.ROUTE_BLUETOOTH, watchDevice, BLUETOOTH_DEVICES);
+        verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+                any(CallAudioState.class), eq(expectedState));
+
+        // Hardcode signal from BT stack signaling to Telecom that watch is now the active device.
+        // This should just be a no-op since audio was already routed when processing active focus.
+        mController.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT,
+                AudioRoute.TYPE_BLUETOOTH_SCO, scoDeviceAddress);
+        verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+                any(CallAudioState.class), eq(expectedState));
+
+        // Mimic behavior of controller processing BT_AUDIO_DISCONNECTED for BLUETOOTH_DEVICE_1 and
+        // verify that audio remains routed to the watch and not routed to earpiece (this should
+        // be taking into account what the BT active device is as reported to us by the BT stack).
+        mController.sendMessageWithSessionInfo(SWITCH_BASELINE_ROUTE,
+                INCLUDE_BLUETOOTH_IN_BASELINE, BLUETOOTH_DEVICE_1.getAddress());
+        verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
+                any(CallAudioState.class), eq(expectedState));
+
+        BLUETOOTH_DEVICES.remove(watchDevice);
+    }
+
+
+    @Test
+    @SmallTest
     public void testAbandonCallAudioFocusAfterCallEnd() {
         // Make sure in-band ringing is disabled so that route never becomes active
         when(mBluetoothRouteManager.isInbandRingEnabled(eq(BLUETOOTH_DEVICE_1))).thenReturn(false);
@@ -1193,7 +1279,17 @@
         if (audioType == AudioRoute.TYPE_BLUETOOTH_SCO) {
             verify(mBluetoothDeviceManager, timeout(TEST_TIMEOUT)).disconnectSco();
         } else {
-            verify(mAudioManager, timeout(TEST_TIMEOUT)).clearCommunicationDevice();
+            if (mFeatureFlags.onlyClearCommunicationDeviceOnInactive()) {
+                verify(mAudioManager, timeout(TEST_TIMEOUT).times(2))
+                        .setCommunicationDevice(any(AudioDeviceInfo.class));
+                // Don't use a timeout here because that will cause the test to pause for a long
+                // period of time to verify; the previous verify has a timeout on it, so it will
+                // have already waited for any AudioManager invocations to take place.  Any
+                // potential clear would have happened by now.
+                verify(mAudioManager, never()).clearCommunicationDevice();
+            } else {
+                verify(mAudioManager, timeout(TEST_TIMEOUT)).clearCommunicationDevice();
+            }
         }
         verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
                 any(CallAudioState.class), eq(expectedState));
diff --git a/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java b/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java
index d1427db..d97263d 100644
--- a/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallScreeningServiceFilterTest.java
@@ -17,6 +17,7 @@
 package com.android.server.telecom.tests;
 
 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;
@@ -136,7 +137,7 @@
         when(mContext.getSystemService(TelecomManager.class))
                 .thenReturn(mTelecomManager);
         when(mTelecomManager.getSystemDialerPackage()).thenReturn(PKG_NAME);
-        when(mAppLabelProxy.getAppLabel(PKG_NAME)).thenReturn(APP_NAME);
+        when(mAppLabelProxy.getAppLabel(PKG_NAME, PA_HANDLE.getUserHandle())).thenReturn(APP_NAME);
         when(mParcelableCallUtilsConverter.toParcelableCall(
                 eq(mCall), anyBoolean(), eq(mPhoneAccountRegistrar))).thenReturn(null);
         when(mContext.bindServiceAsUser(nullable(Intent.class), nullable(ServiceConnection.class),
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index 79fd3d5..34d8830 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -3803,9 +3803,9 @@
         Call callSpy = Mockito.spy(ongoingCall);
 
         // Mocks some methods to not call the real method.
-        doNothing().when(callSpy).unhold();
-        doNothing().when(callSpy).hold();
-        doNothing().when(callSpy).answer(ArgumentMatchers.anyInt());
+        doReturn(null).when(callSpy).unhold();
+        doReturn(null).when(callSpy).hold();
+        doReturn(null).when(callSpy).answer(ArgumentMatchers.anyInt());
         doNothing().when(callSpy).setStartWithSpeakerphoneOn(ArgumentMatchers.anyBoolean());
 
         mCallsManager.addCall(callSpy);
@@ -3817,10 +3817,10 @@
         Call callSpy = Mockito.spy(ongoingCall);
 
         // Mocks some methods to not call the real method.
-        doNothing().when(callSpy).unhold();
-        doNothing().when(callSpy).hold();
-        doNothing().when(callSpy).disconnect();
-        doNothing().when(callSpy).answer(ArgumentMatchers.anyInt());
+        doReturn(null).when(callSpy).unhold();
+        doReturn(null).when(callSpy).hold();
+        doReturn(null).when(callSpy).disconnect();
+        doReturn(null).when(callSpy).answer(ArgumentMatchers.anyInt());
         doNothing().when(callSpy).setStartWithSpeakerphoneOn(ArgumentMatchers.anyBoolean());
 
         return callSpy;
diff --git a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
index 519e596..b6c3743 100644
--- a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
+++ b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
@@ -113,8 +113,7 @@
     private final String PACKAGE_1 = "PACKAGE_1";
     private final String PACKAGE_2 = "PACKAGE_2";
     private final String COMPONENT_NAME = "com.android.server.telecom.tests.MockConnectionService";
-    private final UserHandle USER_HANDLE_10 = UserHandle.of(10);
-    private final UserHandle USER_HANDLE_1000 = UserHandle.of(1000);
+    private final UserHandle USER_HANDLE_10 = new UserHandle(10);
     private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
     private PhoneAccountRegistrar mRegistrar;
     @Mock private SubscriptionManager mSubscriptionManager;
@@ -136,12 +135,11 @@
                 .delete();
         when(mDefaultDialerCache.getDefaultDialerApplication(anyInt()))
                 .thenReturn("com.android.dialer");
-        when(mAppLabelProxy.getAppLabel(anyString()))
+        when(mAppLabelProxy.getAppLabel(anyString(), any()))
                 .thenReturn(TEST_LABEL);
         mRegistrar = new PhoneAccountRegistrar(
                 mComponentContextFixture.getTestDouble().getApplicationContext(), mLock, FILE_NAME,
                 mDefaultDialerCache, mAppLabelProxy, mTelephonyFeatureFlags, mFeatureFlags);
-        mRegistrar.setCurrentUserHandle(UserHandle.SYSTEM);
         when(mFeatureFlags.onlyUpdateTelephonyOnValidSubIds()).thenReturn(false);
         when(mFeatureFlags.unregisterUnresolvableAccounts()).thenReturn(true);
         when(mTelephonyFeatureFlags.workProfileApiSplit()).thenReturn(false);
@@ -1308,7 +1306,8 @@
                 Mockito.mock(IConnectionService.class));
         UserManager userManager = mContext.getSystemService(UserManager.class);
 
-        List<UserHandle> users = Arrays.asList(UserHandle.SYSTEM, USER_HANDLE_1000);
+        List<UserHandle> users = Arrays.asList(new UserHandle(0),
+                new UserHandle(1000));
 
         PhoneAccount pa1 = new PhoneAccount.Builder(
                 new PhoneAccountHandle(new ComponentName(PACKAGE_1, COMPONENT_NAME), "1234",
@@ -1607,7 +1606,7 @@
                 .setCapabilities(PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS);
 
         // WHEN
-        when(mAppLabelProxy.getAppLabel(anyString())).thenReturn(invalidLabel);
+        when(mAppLabelProxy.getAppLabel(anyString(), any())).thenReturn(invalidLabel);
 
         // THEN
         try {
diff --git a/tests/src/com/android/server/telecom/tests/RingerTest.java b/tests/src/com/android/server/telecom/tests/RingerTest.java
index c4d9678..46916fd 100644
--- a/tests/src/com/android/server/telecom/tests/RingerTest.java
+++ b/tests/src/com/android/server/telecom/tests/RingerTest.java
@@ -66,6 +66,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.server.telecom.AnomalyReporterAdapter;
 import com.android.server.telecom.AsyncRingtonePlayer;
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallState;
@@ -123,6 +124,7 @@
     @Mock NotificationManager mockNotificationManager;
     @Mock Ringer.AccessibilityManagerAdapter mockAccessibilityManagerAdapter;
     @Mock private FeatureFlags mFeatureFlags;
+    @Mock private AnomalyReporterAdapter mAnomalyReporterAdapter;
 
     @Spy Ringer.VibrationEffectProxy spyVibrationEffectProxy;
 
@@ -178,7 +180,7 @@
         mRingerUnderTest = new Ringer(mockPlayerFactory, mContext, mockSystemSettingsUtil,
                 asyncRingtonePlayer, mockRingtoneFactory, mockVibrator, spyVibrationEffectProxy,
                 mockInCallController, mockNotificationManager, mockAccessibilityManagerAdapter,
-                mFeatureFlags);
+                mFeatureFlags, mAnomalyReporterAdapter);
         // This future is used to wait for AsyncRingtonePlayer to finish its part.
         mRingerUnderTest.setBlockOnRingingFuture(mRingCompletionFuture);
     }
diff --git a/tests/src/com/android/server/telecom/tests/TelecomPulledAtomTest.java b/tests/src/com/android/server/telecom/tests/TelecomPulledAtomTest.java
index 8ae734c..d3c7859 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomPulledAtomTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomPulledAtomTest.java
@@ -207,12 +207,14 @@
         createTestFileForApiStats(System.currentTimeMillis() - MIN_PULL_INTERVAL_MILLIS - 1);
         ApiStats apiStats = spy(new ApiStats(mSpyContext, mLooper));
         final List<StatsEvent> data = new ArrayList<>();
+        int sizePulled = apiStats.mPulledAtoms.telecomApiStats.length;
 
         int result = apiStats.pull(data);
 
         assertEquals(StatsManager.PULL_SUCCESS, result);
         verify(apiStats).onPull(eq(data));
-        assertEquals(data.size(), apiStats.mPulledAtoms.telecomApiStats.length);
+        assertEquals(data.size(), sizePulled);
+        assertEquals(apiStats.mPulledAtoms.telecomApiStats.length, 0);
     }
 
     @Test
@@ -233,12 +235,14 @@
         createTestFileForAudioRouteStats(System.currentTimeMillis() - MIN_PULL_INTERVAL_MILLIS - 1);
         AudioRouteStats audioRouteStats = spy(new AudioRouteStats(mSpyContext, mLooper));
         final List<StatsEvent> data = new ArrayList<>();
+        int sizePulled = audioRouteStats.mPulledAtoms.callAudioRouteStats.length;
 
         int result = audioRouteStats.pull(data);
 
         assertEquals(StatsManager.PULL_SUCCESS, result);
         verify(audioRouteStats).onPull(eq(data));
-        assertEquals(data.size(), audioRouteStats.mPulledAtoms.callAudioRouteStats.length);
+        assertEquals(data.size(), sizePulled);
+        assertEquals(audioRouteStats.mPulledAtoms.callAudioRouteStats.length, 0);
     }
 
     @Test
@@ -259,12 +263,14 @@
         createTestFileForCallStats(System.currentTimeMillis() - MIN_PULL_INTERVAL_MILLIS - 1);
         CallStats callStats = spy(new CallStats(mSpyContext, mLooper));
         final List<StatsEvent> data = new ArrayList<>();
+        int sizePulled = callStats.mPulledAtoms.callStats.length;
 
         int result = callStats.pull(data);
 
         assertEquals(StatsManager.PULL_SUCCESS, result);
         verify(callStats).onPull(eq(data));
-        assertEquals(data.size(), callStats.mPulledAtoms.callStats.length);
+        assertEquals(data.size(), sizePulled);
+        assertEquals(callStats.mPulledAtoms.callStats.length, 0);
     }
 
     @Test
@@ -285,12 +291,14 @@
         createTestFileForErrorStats(System.currentTimeMillis() - MIN_PULL_INTERVAL_MILLIS - 1);
         ErrorStats errorStats = spy(new ErrorStats(mSpyContext, mLooper));
         final List<StatsEvent> data = new ArrayList<>();
+        int sizePulled = errorStats.mPulledAtoms.telecomErrorStats.length;
 
         int result = errorStats.pull(data);
 
         assertEquals(StatsManager.PULL_SUCCESS, result);
         verify(errorStats).onPull(eq(data));
-        assertEquals(data.size(), errorStats.mPulledAtoms.telecomErrorStats.length);
+        assertEquals(data.size(), sizePulled);
+        assertEquals(errorStats.mPulledAtoms.telecomErrorStats.length, 0);
     }
 
     @Test
diff --git a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
index 07a12c4..220d783 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
@@ -93,6 +93,7 @@
 import com.android.server.telecom.PhoneAccountRegistrar;
 import com.android.server.telecom.TelecomServiceImpl;
 import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.callsequencing.CallTransaction;
 import com.android.server.telecom.components.UserCallIntentProcessor;
 import com.android.server.telecom.components.UserCallIntentProcessorFactory;
 import com.android.server.telecom.flags.FeatureFlags;
@@ -116,6 +117,7 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Executor;
 import java.util.function.IntConsumer;
 
@@ -206,9 +208,12 @@
 
     @Mock private InCallController mInCallController;
     @Mock private TelecomMetricsController mMockTelecomMetricsController;
+    @Mock private OutgoingCallTransaction mOutgoingCallTransaction;
+    @Mock private IncomingCallTransaction mIncomingCallTransaction;
 
     private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
 
+    private static final String SYSTEM_UI_PACKAGE = "com.android.systemui";
     private static final String DEFAULT_DIALER_PACKAGE = "com.google.android.dialer";
     private static final UserHandle USER_HANDLE_16 = new UserHandle(16);
     private static final UserHandle USER_HANDLE_17 = new UserHandle(17);
@@ -260,7 +265,8 @@
                 mFeatureFlags,
                 mTelephonyFeatureFlags,
                 mLock,
-                mMockTelecomMetricsController);
+                mMockTelecomMetricsController,
+                SYSTEM_UI_PACKAGE);
         telecomServiceImpl.setTransactionManager(mTransactionManager);
         telecomServiceImpl.setAnomalyReporterAdapter(mAnomalyReporterAdapter);
         mTSIBinder = telecomServiceImpl.getBinder();
@@ -280,6 +286,7 @@
         when(mPackageManager.getPackageUid(anyString(), eq(0))).thenReturn(Binder.getCallingUid());
         when(mFeatureFlags.earlyBindingToIncallService()).thenReturn(true);
         when(mTelephonyFeatureFlags.workProfileApiSplit()).thenReturn(false);
+        when(mFeatureFlags.enableCallSequencing()).thenReturn(false);
     }
 
     @Override
@@ -455,6 +462,9 @@
         // WHEN
         when(mFakePhoneAccountRegistrar.getPhoneAccountUnchecked(TEL_PA_HANDLE_CURRENT)).thenReturn(
                 phoneAccount);
+        when(mFakeCallsManager.createTransactionalCall(any(String.class),
+                any(CallAttributes.class), any(Bundle.class), any(String.class)))
+                .thenReturn(CompletableFuture.completedFuture(mOutgoingCallTransaction));
 
         doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccount(
                 eq(TEL_PA_HANDLE_CURRENT), any(UserHandle.class));
@@ -483,6 +493,9 @@
 
         doReturn(phoneAccount).when(mFakePhoneAccountRegistrar).getPhoneAccount(
                 eq(TEL_PA_HANDLE_CURRENT), any(UserHandle.class));
+        when(mFakeCallsManager.createTransactionalCall(any(String.class),
+                any(CallAttributes.class), any(Bundle.class), any(String.class)))
+                .thenReturn(CompletableFuture.completedFuture(mIncomingCallTransaction));
 
         mTSIBinder.addCall(mIncomingCallAttributes, mICallEventCallback, "1", CALLING_PACKAGE);
 
@@ -2307,7 +2320,8 @@
     }
 
     /**
-     * Ensure self-managed calls cannot be ended using {@link TelecomManager#endCall()}.
+     * Ensure self-managed calls cannot be ended using {@link TelecomManager#endCall()} when the
+     * caller of this method is not considered privileged.
      * @throws Exception
      */
     @SmallTest
@@ -2324,7 +2338,8 @@
 
     /**
      * Ensure self-managed calls cannot be answered using {@link TelecomManager#acceptRingingCall()}
-     * or {@link TelecomManager#acceptRingingCall(int)}.
+     * or {@link TelecomManager#acceptRingingCall(int)} when the caller of these methods is not
+     * considered privileged.
      * @throws Exception
      */
     @SmallTest
@@ -2339,6 +2354,53 @@
         verify(mFakeCallsManager, never()).answerCall(eq(call), anyInt());
     }
 
+    /**
+     * Ensure self-managed calls can be answered using {@link TelecomManager#acceptRingingCall()}
+     * or {@link TelecomManager#acceptRingingCall(int)} if the caller of these methods is
+     * privileged.
+     * @throws Exception
+     */
+    @SmallTest
+    @Test
+    public void testCanAnswerSelfManagedCallIfPrivileged() throws Exception {
+        when(mFeatureFlags.allowSystemAppsResolveVoipCalls()).thenReturn(true);
+        // Configure the test so that the caller of acceptRingingCall is considered privileged:
+        when(mPackageManager.getPackageUid(SYSTEM_UI_PACKAGE, 0))
+                .thenReturn(Binder.getCallingUid());
+
+        // Ensure that the call is successfully accepted:
+        Call call = mock(Call.class);
+        when(call.isSelfManaged()).thenReturn(true);
+        when(call.getState()).thenReturn(CallState.ACTIVE);
+        when(mFakeCallsManager.getFirstCallWithState(any()))
+                .thenReturn(call);
+        mTSIBinder.acceptRingingCall(TEST_PACKAGE);
+        verify(mFakeCallsManager).answerCall(eq(call), anyInt());
+    }
+
+    /**
+     * Ensure self-managed calls can be ended using {@link TelecomManager#endCall()} when the
+     * caller of these methods is privileged.
+     * @throws Exception
+     */
+    @SmallTest
+    @Test
+    public void testCanEndSelfManagedCallIfPrivileged() throws Exception {
+        when(mFeatureFlags.allowSystemAppsResolveVoipCalls()).thenReturn(true);
+        // Configure the test so that the caller of endCall is considered privileged:
+        when(mPackageManager.getPackageUid(SYSTEM_UI_PACKAGE, 0))
+                .thenReturn(Binder.getCallingUid());
+        // Set up the call:
+        Call call = mock(Call.class);
+        when(call.isSelfManaged()).thenReturn(true);
+        when(call.getState()).thenReturn(CallState.ACTIVE);
+        when(mFakeCallsManager.getFirstCallWithState(any()))
+                .thenReturn(call);
+        // Ensure that the call is successfully ended:
+        assertTrue(mTSIBinder.endCall(TEST_PACKAGE));
+        verify(mFakeCallsManager).disconnectCall(eq(call));
+    }
+
     @SmallTest
     @Test
     public void testGetAdnUriForPhoneAccount() throws Exception {
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index 4463d65..1e65011 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -224,6 +224,7 @@
     @Mock
     com.android.internal.telephony.flags.FeatureFlags mTelephonyFlags;
 
+    private static final String SYSTEM_UI_PACKAGE = "com.android.systemui";
     final ComponentName mInCallServiceComponentNameX =
             new ComponentName(
                     "incall-service-package-X",
@@ -580,7 +581,8 @@
                             ContactsAsyncHelper.ContentResolverAdapter adapter) {
                         return new ContactsAsyncHelper(adapter, mHandlerThread.getLooper());
                     }
-                }, mDeviceIdleControllerAdapter, mAccessibilityManagerAdapter,
+                }, mDeviceIdleControllerAdapter, SYSTEM_UI_PACKAGE,
+                mAccessibilityManagerAdapter,
                 Runnable::run,
                 Runnable::run,
                 mBlockedNumbersAdapter,
diff --git a/tests/src/com/android/server/telecom/tests/TransactionTests.java b/tests/src/com/android/server/telecom/tests/TransactionTests.java
index 78c2210..0a23913 100644
--- a/tests/src/com/android/server/telecom/tests/TransactionTests.java
+++ b/tests/src/com/android/server/telecom/tests/TransactionTests.java
@@ -292,20 +292,21 @@
                 .setCallType(CallAttributes.VIDEO_CALL)
                 .build();
 
+        Bundle extras = new Bundle();
         OutgoingCallTransaction t = new OutgoingCallTransaction(null,
-                mContext, null, mCallsManager, new Bundle(), mFeatureFlags);
+                mContext, null, mCallsManager, extras, mFeatureFlags);
 
         // WHEN
         when(mFeatureFlags.transactionalVideoState()).thenReturn(true);
         t.setFeatureFlags(mFeatureFlags);
 
         // THEN
-        assertEquals(VideoProfile.STATE_AUDIO_ONLY, t
-                .generateExtras(audioOnlyAttributes)
+        assertEquals(VideoProfile.STATE_AUDIO_ONLY, OutgoingCallTransaction
+                .generateExtras(null, extras, audioOnlyAttributes, mFeatureFlags)
                 .getInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE));
 
-        assertEquals(VideoProfile.STATE_BIDIRECTIONAL, t
-                .generateExtras(videoAttributes)
+        assertEquals(VideoProfile.STATE_BIDIRECTIONAL, OutgoingCallTransaction
+                .generateExtras(null, extras, videoAttributes, mFeatureFlags)
                 .getInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE));
     }
 
@@ -448,9 +449,9 @@
         callSpy.setState(initialState, "manual set in test");
 
         // Mocks some methods to not call the real method.
-        doNothing().when(callSpy).unhold();
-        doNothing().when(callSpy).hold();
-        doNothing().when(callSpy).disconnect();
+        doReturn(null).when(callSpy).unhold();
+        doReturn(null).when(callSpy).hold();
+        doReturn(null).when(callSpy).disconnect();
 
         return callSpy;
     }