Merge "Flag guard switch to work profile dialog for call" into udc-dev
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 5847960..32b5556 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -17,6 +17,7 @@
 package com.android.server.telecom;
 
 import static android.provider.CallLog.Calls.MISSED_REASON_NOT_MISSED;
+import static android.telecom.Call.EVENT_DISPLAY_SOS_MESSAGE;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -2397,6 +2398,8 @@
 
     @Override
     public void handleCreateConferenceFailure(DisconnectCause disconnectCause) {
+        Log.i(this, "handleCreateConferenceFailure; callid=%s, disconnectCause=%s",
+                getId(), disconnectCause);
         clearConnectionService();
         setDisconnectCause(disconnectCause);
         mCallsManager.markCallAsDisconnected(this, disconnectCause);
@@ -2417,6 +2420,8 @@
 
     @Override
     public void handleCreateConnectionFailure(DisconnectCause disconnectCause) {
+        Log.i(this, "handleCreateConnectionFailure; callid=%s, disconnectCause=%s",
+                getId(), disconnectCause);
         clearConnectionService();
         setDisconnectCause(disconnectCause);
         mCallsManager.markCallAsDisconnected(this, disconnectCause);
@@ -2701,7 +2706,8 @@
             // hangup, not reject.
             setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.REJECTED));
             if (mTransactionalService != null) {
-                mTransactionalService.onReject(this, DisconnectCause.REJECTED);
+                mTransactionalService.onDisconnect(this,
+                        new DisconnectCause(DisconnectCause.REJECTED));
             } else if (mConnectionService != null) {
                 mConnectionService.disconnect(this);
             } else {
@@ -2714,7 +2720,8 @@
             mVideoStateHistory |= mVideoState;
 
             if (mTransactionalService != null) {
-                mTransactionalService.onReject(this, DisconnectCause.REJECTED);
+                mTransactionalService.onDisconnect(this,
+                        new DisconnectCause(DisconnectCause.REJECTED));
             } else if (mConnectionService != null) {
                 mConnectionService.reject(this, rejectWithMessage, textMessage);
             } else {
@@ -2737,7 +2744,8 @@
             // Since its simulated reason we can't pass along the reject reason.
             setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.REJECTED));
             if (mTransactionalService != null) {
-                mTransactionalService.onReject(this, DisconnectCause.REJECTED);
+                mTransactionalService.onDisconnect(this,
+                        new DisconnectCause(DisconnectCause.REJECTED));
             } else if (mConnectionService != null) {
                 mConnectionService.disconnect(this);
             } else {
@@ -2749,7 +2757,8 @@
             // Ensure video state history tracks video state at time of rejection.
             mVideoStateHistory |= mVideoState;
             if (mTransactionalService != null) {
-                mTransactionalService.onReject(this, rejectReason);
+                mTransactionalService.onDisconnect(this,
+                        new DisconnectCause(DisconnectCause.REJECTED));
             } else if (mConnectionService != null) {
                 mConnectionService.rejectWithReason(this, rejectReason);
             } else {
@@ -3205,9 +3214,7 @@
      * @param extras Associated extras.
      */
     public void sendCallEvent(String event, int targetSdkVer, Bundle extras) {
-        if (mTransactionalService != null) {
-            Log.i(this, "sendCallEvent: called on TransactionalService. doing nothing");
-        } else if (mConnectionService != null) {
+        if (mConnectionService != null || mTransactionalService != null) {
             if (android.telecom.Call.EVENT_REQUEST_HANDOVER.equals(event)) {
                 if (targetSdkVer > Build.VERSION_CODES.P) {
                     Log.e(this, new Exception(), "sendCallEvent failed. Use public api handoverTo" +
@@ -3230,8 +3237,8 @@
                 if (extras == null) {
                     Log.w(this, "sendCallEvent: %s event received with null extras.",
                             android.telecom.Call.EVENT_REQUEST_HANDOVER);
-                    mConnectionService.sendCallEvent(this,
-                            android.telecom.Call.EVENT_HANDOVER_FAILED, null);
+                    sendEventToService(this, android.telecom.Call.EVENT_HANDOVER_FAILED,
+                            null);
                     return;
                 }
                 Parcelable parcelable = extras.getParcelable(
@@ -3239,8 +3246,7 @@
                 if (!(parcelable instanceof PhoneAccountHandle) || parcelable == null) {
                     Log.w(this, "sendCallEvent: %s event received with invalid handover acct.",
                             android.telecom.Call.EVENT_REQUEST_HANDOVER);
-                    mConnectionService.sendCallEvent(this,
-                            android.telecom.Call.EVENT_HANDOVER_FAILED, null);
+                    sendEventToService(this, android.telecom.Call.EVENT_HANDOVER_FAILED, null);
                     return;
                 }
                 PhoneAccountHandle phoneAccountHandle = (PhoneAccountHandle) parcelable;
@@ -3263,7 +3269,7 @@
                     ));
                 }
                 Log.addEvent(this, LogUtils.Events.CALL_EVENT, event);
-                mConnectionService.sendCallEvent(this, event, extras);
+                sendEventToService(this, event, extras);
             }
         } else {
             Log.e(this, new NullPointerException(),
@@ -3272,6 +3278,17 @@
     }
 
     /**
+     *  This method should only be called from sendCallEvent(String, int, Bundle).
+     */
+    private void sendEventToService(Call call, String event, Bundle extras) {
+        if (mConnectionService != null) {
+            mConnectionService.sendCallEvent(call, event, extras);
+        } else if (mTransactionalService != null) {
+            mTransactionalService.onEvent(call, event, extras);
+        }
+    }
+
+    /**
      * Notifies listeners when a bluetooth quality report is received.
      * @param report The bluetooth quality report.
      */
@@ -3643,6 +3660,7 @@
         if (mTransactionalService != null) {
             Log.i(this, "stopRtt: called on TransactionalService. doing nothing");
         } else if (mConnectionService != null) {
+            Log.addEvent(this, LogUtils.Events.REQUEST_RTT, "stop");
             mConnectionService.stopRtt(this);
         } else {
             // If this gets called by the in-call app before the connection service is set, we'll
@@ -3656,6 +3674,7 @@
             Log.i(this, "sendRttRequest: called on TransactionalService. doing nothing");
             return;
         }
+        Log.addEvent(this, LogUtils.Events.REQUEST_RTT, "start");
         createRttStreams();
         mConnectionService.startRtt(this, getInCallToCsRttPipeForCs(), getCsToInCallRttPipeForCs());
     }
@@ -3679,12 +3698,14 @@
 
     public void onRttConnectionFailure(int reason) {
         Log.i(this, "Got RTT initiation failure with reason %d", reason);
+        Log.addEvent(this, LogUtils.Events.ON_RTT_FAILED, "reason="  + reason);
         for (Listener l : mListeners) {
             l.onRttInitiationFailure(this, reason);
         }
     }
 
     public void onRemoteRttRequest() {
+        Log.addEvent(this, LogUtils.Events.ON_RTT_REQUEST);
         if (isRttCall()) {
             Log.w(this, "Remote RTT request on a call that's already RTT");
             return;
@@ -3709,6 +3730,8 @@
             Log.i(this, "handleRttRequestResponse: called on TransactionalService. doing nothing");
             return;
         }
+        Log.addEvent(this, LogUtils.Events.RESPOND_TO_RTT_REQUEST, "id=" + id + ", accept="
+                + accept);
         if (accept) {
             createRttStreams();
             Log.i(this, "RTT request %d accepted.", id);
@@ -3985,7 +4008,8 @@
 
     public void setRttMode(int mode) {
         mRttMode = mode;
-        // TODO: hook this up to CallAudioManager
+        Log.addEvent(this, LogUtils.Events.SET_RRT_MODE, "mode=" + mode);
+        // TODO: hook this up to CallAudioManager.
     }
 
     /**
@@ -4060,6 +4084,12 @@
                 l.onReceivedCallQualityReport(this, callQuality);
             }
         } else {
+            if (event.equals(EVENT_DISPLAY_SOS_MESSAGE) && !isEmergencyCall()) {
+                Log.w(this, "onConnectionEvent: EVENT_DISPLAY_SOS_MESSAGE is sent "
+                        + "without an emergency call");
+                return;
+            }
+
             for (Listener l : mListeners) {
                 l.onConnectionEvent(this, event, extras);
             }
diff --git a/src/com/android/server/telecom/CallAnomalyWatchdog.java b/src/com/android/server/telecom/CallAnomalyWatchdog.java
index d3dbbc9..b46a6cf 100644
--- a/src/com/android/server/telecom/CallAnomalyWatchdog.java
+++ b/src/com/android/server/telecom/CallAnomalyWatchdog.java
@@ -165,23 +165,26 @@
     }
 
     /**
+     * Override of {@link CallsManagerListenerBase} to track when calls are created but
+     * intentionally not added to mCalls. These calls should no longer be tracked by the
+     * CallAnomalyWatchdog.
+     * @param call the call
+     */
+
+    @Override
+    public void onCallCreatedButNeverAdded(Call call) {
+        Log.i(this, "onCallCreatedButNeverAdded: call=%s", call.toString());
+        stopTrackingCall(call);
+    }
+
+    /**
      * Override of {@link CallsManagerListenerBase} to track when calls are removed
      * @param call the call
      */
     @Override
     public void onCallRemoved(Call call) {
-        if (mScheduledFutureMap.containsKey(call)) {
-            ScheduledFuture<?> existingTimeout = mScheduledFutureMap.get(call);
-            existingTimeout.cancel(false /* cancelIfRunning */);
-            mScheduledFutureMap.remove(call);
-        }
-        if (mCallsPendingDestruction.contains(call)) {
-            mCallsPendingDestruction.remove(call);
-        }
-        if (mWatchdogCallStateMap.containsKey(call)) {
-            mWatchdogCallStateMap.remove(call);
-        }
-        call.removeListener(this);
+        Log.i(this, "onCallRemoved: call=%s", call.toString());
+        stopTrackingCall(call);
     }
 
     /**
@@ -192,7 +195,10 @@
      * @param newState the new state
      */
     @Override
-    public void onCallStateChanged(Call call, int oldState, int newState) { maybeTrackCall(call); }
+    public void onCallStateChanged(Call call, int oldState, int newState) {
+        Log.i(this, "onCallStateChanged: call=%s", call.toString());
+        maybeTrackCall(call);
+    }
 
     /**
      * Override of {@link Call.Listener} so we can capture successful creation of calls.
@@ -211,7 +217,8 @@
      */
     @Override
     public void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause) {
-        maybeTrackCall(call);
+        Log.i(this, "onFailedOutgoingCall: call=%s", call.toString());
+        stopTrackingCall(call);
     }
 
     /**
@@ -229,7 +236,27 @@
      */
     @Override
     public void onFailedIncomingCall(Call call) {
-        maybeTrackCall(call);
+        Log.i(this, "onFailedIncomingCall: call=%s", call.toString());
+        stopTrackingCall(call);
+    }
+
+    /**
+     * Helper method used to stop CallAnomalyWatchdog from tracking or destroying the call.
+     * @param call the call.
+     */
+    private void stopTrackingCall(Call call) {
+        if (mScheduledFutureMap.containsKey(call)) {
+            ScheduledFuture<?> existingTimeout = mScheduledFutureMap.get(call);
+            existingTimeout.cancel(false /* cancelIfRunning */);
+            mScheduledFutureMap.remove(call);
+        }
+        if (mCallsPendingDestruction.contains(call)) {
+            mCallsPendingDestruction.remove(call);
+        }
+        if (mWatchdogCallStateMap.containsKey(call)) {
+            mWatchdogCallStateMap.remove(call);
+        }
+        call.removeListener(this);
     }
 
     /**
@@ -273,7 +300,7 @@
         }
     }
 
-    private long getTimeoutMillis(Call call, WatchdogCallState state) {
+    public long getTimeoutMillis(Call call, WatchdogCallState state) {
         boolean isVoip = call.getIsVoipAudioMode();
         boolean isEmergency = call.isEmergencyCall();
 
diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
index f56320f..cccade3 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -49,6 +49,7 @@
 import java.util.HashMap;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.Executor;
 
 /**
  * This class describes the available routes of a call as a state machine.
@@ -81,14 +82,16 @@
                 WiredHeadsetManager wiredHeadsetManager,
                 StatusBarNotifier statusBarNotifier,
                 CallAudioManager.AudioServiceFactory audioServiceFactory,
-                int earpieceControl) {
+                int earpieceControl,
+                Executor asyncTaskExecutor) {
             return new CallAudioRouteStateMachine(context,
                     callsManager,
                     bluetoothManager,
                     wiredHeadsetManager,
                     statusBarNotifier,
                     audioServiceFactory,
-                    earpieceControl);
+                    earpieceControl,
+                    asyncTaskExecutor);
         }
     }
     /** Values for CallAudioRouteStateMachine constructor's earPieceRouting arg. */
@@ -1478,6 +1481,8 @@
     private final QuiescentSpeakerRoute mQuiescentSpeakerRoute = new QuiescentSpeakerRoute();
     private final StreamingState mStreamingState = new StreamingState();
 
+    private final Executor mAsyncTaskExecutor;
+
     /**
      * A few pieces of hidden state. Used to avoid exponential explosion of number of explicit
      * states
@@ -1516,7 +1521,8 @@
             WiredHeadsetManager wiredHeadsetManager,
             StatusBarNotifier statusBarNotifier,
             CallAudioManager.AudioServiceFactory audioServiceFactory,
-            int earpieceControl) {
+            int earpieceControl,
+            Executor asyncTaskExecutor) {
         super(NAME);
         mContext = context;
         mCallsManager = callsManager;
@@ -1526,7 +1532,7 @@
         mStatusBarNotifier = statusBarNotifier;
         mAudioServiceFactory = audioServiceFactory;
         mLock = callsManager.getLock();
-
+        mAsyncTaskExecutor = asyncTaskExecutor;
         createStates(earpieceControl);
     }
 
@@ -1538,7 +1544,7 @@
             WiredHeadsetManager wiredHeadsetManager,
             StatusBarNotifier statusBarNotifier,
             CallAudioManager.AudioServiceFactory audioServiceFactory,
-            int earpieceControl, Looper looper) {
+            int earpieceControl, Looper looper, Executor asyncTaskExecutor) {
         super(NAME, looper);
         mContext = context;
         mCallsManager = callsManager;
@@ -1548,6 +1554,7 @@
         mStatusBarNotifier = statusBarNotifier;
         mAudioServiceFactory = audioServiceFactory;
         mLock = callsManager.getLock();
+        mAsyncTaskExecutor = asyncTaskExecutor;
 
         createStates(earpieceControl);
     }
@@ -1629,7 +1636,8 @@
                 new IntentFilter(AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED));
 
         mStatusBarNotifier.notifyMute(initState.isMuted());
-        mStatusBarNotifier.notifySpeakerphone(initState.getRoute() == CallAudioState.ROUTE_SPEAKER);
+        // We used to call mStatusBarNotifier.notifySpeakerphone, but that makes no sense as there
+        // is never a call at this boot (init) time.
         setInitialState(mRouteCodeToQuiescentState.get(initState.getRoute()));
         start();
     }
@@ -1722,26 +1730,32 @@
 
     private void setSpeakerphoneOn(boolean on) {
         Log.i(this, "turning speaker phone %s", on);
-        AudioDeviceInfo speakerDevice = null;
-        for (AudioDeviceInfo info : mAudioManager.getAvailableCommunicationDevices()) {
-            if (info.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
-                speakerDevice = info;
-                break;
+        final boolean hasAnyCalls = mCallsManager.hasAnyCalls();
+        // These APIs are all via two-way binder calls so can potentially block Telecom.  Since none
+        // of this has to happen in the Telecom lock we'll offload it to the async executor.
+        mAsyncTaskExecutor.execute(() -> {
+            AudioDeviceInfo speakerDevice = null;
+            for (AudioDeviceInfo info : mAudioManager.getAvailableCommunicationDevices()) {
+                if (info.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
+                    speakerDevice = info;
+                    break;
+                }
             }
-        }
-        boolean speakerOn = false;
-        if (speakerDevice != null && on) {
-            boolean result = mAudioManager.setCommunicationDevice(speakerDevice);
-            if (result) {
-                speakerOn = true;
+            boolean speakerOn = false;
+            if (speakerDevice != null && on) {
+                boolean result = mAudioManager.setCommunicationDevice(speakerDevice);
+                if (result) {
+                    speakerOn = true;
+                }
+            } else {
+                AudioDeviceInfo curDevice = mAudioManager.getCommunicationDevice();
+                if (curDevice != null
+                        && curDevice.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
+                    mAudioManager.clearCommunicationDevice();
+                }
             }
-        } else {
-            AudioDeviceInfo curDevice = mAudioManager.getCommunicationDevice();
-            if (curDevice != null && curDevice.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
-                mAudioManager.clearCommunicationDevice();
-            }
-        }
-        mStatusBarNotifier.notifySpeakerphone(speakerOn);
+            mStatusBarNotifier.notifySpeakerphone(hasAnyCalls && speakerOn);
+        });
     }
 
     private void setBluetoothOn(String address) {
diff --git a/src/com/android/server/telecom/CallStreamingController.java b/src/com/android/server/telecom/CallStreamingController.java
index 56ce17b..e494d8e 100644
--- a/src/com/android/server/telecom/CallStreamingController.java
+++ b/src/com/android/server/telecom/CallStreamingController.java
@@ -111,11 +111,11 @@
 
             if (mCallsManager.getCallStreamingController().isStreaming()) {
                 future.complete(new VoipCallTransactionResult(
-                        VoipCallTransactionResult.RESULT_SUCCEED, null));
-            } else {
-                future.complete(new VoipCallTransactionResult(
                         VoipCallTransactionResult.RESULT_FAILED,
                         "STREAMING_FAILED_ALREADY_STREAMING"));
+            } else {
+                future.complete(new VoipCallTransactionResult(
+                        VoipCallTransactionResult.RESULT_SUCCEED, null));
             }
 
             return future;
@@ -177,7 +177,7 @@
             CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
 
             RoleManager roleManager = mContext.getSystemService(RoleManager.class);
-            PackageManager packageManager = mContext.getSystemService(PackageManager.class);
+            PackageManager packageManager = mContext.getPackageManager();
             if (roleManager == null || packageManager == null) {
                 Log.e(TAG, "Can't find system service");
                 future.complete(new VoipCallTransactionResult(
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 5d5a103..5a7d3de 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -155,6 +155,7 @@
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
@@ -182,6 +183,7 @@
          */
         default void onCallCreated(Call call) {}
         void onCallAdded(Call call);
+        void onCallCreatedButNeverAdded(Call call);
         void onCallRemoved(Call call);
         void onCallStateChanged(Call call, int oldState, int newState);
         void onConnectionServiceChanged(
@@ -279,6 +281,15 @@
     public static final String EXCEPTION_RETRIEVING_PHONE_ACCOUNTS_EMERGENCY_ERROR_MSG =
             "Exception thrown while retrieving list of potential phone accounts when placing an "
                     + "emergency call.";
+    public static final UUID EMERGENCY_CALL_DISCONNECTED_BEFORE_BEING_ADDED_ERROR_UUID =
+            UUID.fromString("f9a916c8-8d61-4550-9ad3-11c2e84f6364");
+    public static final String EMERGENCY_CALL_DISCONNECTED_BEFORE_BEING_ADDED_ERROR_MSG =
+            "An emergency call was disconnected after the connection was created but before the "
+                    + "call was successfully added to CallsManager.";
+    public static final UUID EMERGENCY_CALL_ABORTED_NO_PHONE_ACCOUNTS_ERROR_UUID =
+            UUID.fromString("2e994acb-1997-4345-8bf3-bad04303de26");
+    public static final String EMERGENCY_CALL_ABORTED_NO_PHONE_ACCOUNTS_ERROR_MSG =
+            "An emergency call was aborted since there were no available phone accounts.";
 
     private static final int[] OUTGOING_CALL_STATES =
             {CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING,
@@ -450,12 +461,13 @@
 
     private boolean mCanAddCall = true;
 
-    private int mMaxNumberOfSimultaneouslyActiveSims = -1;
-
     private Runnable mStopTone;
 
     private LinkedList<HandlerThread> mGraphHandlerThreads;
 
+    // An executor that can be used to fire off async tasks that do not block Telecom in any manner.
+    private final Executor mAsyncTaskExecutor;
+
     private boolean mHasActiveRttCall = false;
 
     private AnomalyReporterAdapter mAnomalyReporter = new AnomalyReporterAdapterImpl();
@@ -554,7 +566,9 @@
             RoleManagerAdapter roleManagerAdapter,
             ToastFactory toastFactory,
             CallEndpointControllerFactory callEndpointControllerFactory,
-            CallAnomalyWatchdog callAnomalyWatchdog) {
+            CallAnomalyWatchdog callAnomalyWatchdog,
+            Ringer.AccessibilityManagerAdapter accessibilityManagerAdapter,
+            Executor asyncTaskExecutor) {
         mContext = context;
         mLock = lock;
         mPhoneNumberUtilsAdapter = phoneNumberUtilsAdapter;
@@ -582,7 +596,8 @@
                         wiredHeadsetManager,
                         statusBarNotifier,
                         audioServiceFactory,
-                        CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT
+                        CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT,
+                        asyncTaskExecutor
                 );
         callAudioRouteStateMachine.initialize();
 
@@ -615,7 +630,7 @@
                 ringtoneFactory, systemVibrator,
                 new Ringer.VibrationEffectProxy(), mInCallController,
                 mContext.getSystemService(NotificationManager.class),
-                mContext.getSystemService(AccessibilityManager.class));
+                accessibilityManagerAdapter);
         mCallRecordingTonePlayer = new CallRecordingTonePlayer(mContext, audioManager,
                 mTimeoutsAdapter, mLock);
         mCallAudioManager = new CallAudioManager(callAudioRouteStateMachine,
@@ -671,6 +686,7 @@
         mGraphHandlerThreads = new LinkedList<>();
 
         mCallAnomalyWatchdog = callAnomalyWatchdog;
+        mAsyncTaskExecutor = asyncTaskExecutor;
     }
 
     public void setIncomingCallNotifier(IncomingCallNotifier incomingCallNotifier) {
@@ -729,8 +745,7 @@
 
     @Override
     public void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause) {
-        Log.v(this, "onFailedOutgoingCall, call: %s", call);
-
+        Log.i(this, "onFailedOutgoingCall for call %s", call);
         markCallAsRemoved(call);
     }
 
@@ -985,14 +1000,15 @@
 
     @Override
     public void onFailedIncomingCall(Call call) {
+        Log.i(this, "onFailedIncomingCall for call %s", call);
         setCallState(call, CallState.DISCONNECTED, "failed incoming call");
         call.removeListener(this);
     }
 
     @Override
     public void onSuccessfulUnknownCall(Call call, int callState) {
-        setCallState(call, callState, "successful unknown call");
         Log.i(this, "onSuccessfulUnknownCall for call %s", call);
+        setCallState(call, callState, "successful unknown call");
         addCall(call);
     }
 
@@ -1545,6 +1561,8 @@
             // transactional calls should skip Call#startCreateConnection below
             // as that is meant for Call objects with a ConnectionServiceWrapper
             call.setState(CallState.RINGING, "explicitly set new incoming to ringing");
+            // Transactional calls don't get created via a connection service; they are added now.
+            call.setIsCreateConnectionComplete(true);
             addCall(call);
         } else {
             call.startCreateConnection(mPhoneAccountRegistrar);
@@ -1941,6 +1959,12 @@
                                 Log.i(CallsManager.this, "Aborting call since there are no"
                                         + " available accounts.");
                                 showErrorMessage(R.string.cant_call_due_to_no_supported_service);
+                                mListeners.forEach(l -> l.onCallCreatedButNeverAdded(callToPlace));
+                                if (callToPlace.isEmergencyCall()){
+                                    mAnomalyReporter.reportAnomaly(
+                                            EMERGENCY_CALL_ABORTED_NO_PHONE_ACCOUNTS_ERROR_UUID,
+                                            EMERGENCY_CALL_ABORTED_NO_PHONE_ACCOUNTS_ERROR_MSG);
+                                }
                                 return CompletableFuture.completedFuture(null);
                             }
                             boolean needsAccountSelection = accountSuggestions.size() > 1
@@ -3120,6 +3144,18 @@
         return constructPossiblePhoneAccounts(handle, user, isVideo, isEmergency, false);
     }
 
+    // Returns whether the device is capable of 2 simultaneous active voice calls on different subs.
+    private boolean isDsdaCallingPossible() {
+        try {
+            return getTelephonyManager().getMaxNumberOfSimultaneouslyActiveSims() > 1
+                    || getTelephonyManager().getPhoneCapability()
+                           .getMaxActiveVoiceSubscriptions() > 1;
+        } catch (Exception e) {
+            Log.w(this, "exception in isDsdaCallingPossible(): ", e);
+            return false;
+        }
+    }
+
     public List<PhoneAccountHandle> constructPossiblePhoneAccounts(Uri handle, UserHandle user,
             boolean isVideo, boolean isEmergency, boolean isConference) {
 
@@ -3136,13 +3172,9 @@
                         capabilities,
                         isEmergency ? 0 : PhoneAccount.CAPABILITY_EMERGENCY_CALLS_ONLY,
                         isEmergency);
-        if (mMaxNumberOfSimultaneouslyActiveSims < 0) {
-            mMaxNumberOfSimultaneouslyActiveSims =
-                    getTelephonyManager().getMaxNumberOfSimultaneouslyActiveSims();
-        }
         // Only one SIM PhoneAccount can be active at one time for DSDS. Only that SIM PhoneAccount
         // should be available if a call is already active on the SIM account.
-        if (mMaxNumberOfSimultaneouslyActiveSims == 1) {
+        if (!isDsdaCallingPossible()) {
             List<PhoneAccountHandle> simAccounts =
                     mPhoneAccountRegistrar.getSimPhoneAccountsOfCurrentUser();
             PhoneAccountHandle ongoingCallAccount = null;
@@ -3337,7 +3369,8 @@
         setCallState(call, CallState.RINGING, "ringing set explicitly");
     }
 
-    void markCallAsDialing(Call call) {
+    @VisibleForTesting
+    public void markCallAsDialing(Call call) {
         setCallState(call, CallState.DIALING, "dialing set explicitly");
         maybeMoveToSpeakerPhone(call);
         maybeTurnOffMute(call);
@@ -3493,6 +3526,8 @@
      * @param disconnectCause The disconnect cause, see {@link android.telecom.DisconnectCause}.
      */
     public void markCallAsDisconnected(Call call, DisconnectCause disconnectCause) {
+        Log.i(this, "markCallAsDisconnected: call=%s; disconnectCause=%s",
+                call.toString(), disconnectCause.toString());
         int oldState = call.getState();
         if (call.getState() == CallState.SIMULATED_RINGING
                 && disconnectCause.getCode() == DisconnectCause.REMOTE) {
@@ -3517,6 +3552,17 @@
             }
         }
 
+        // Notify listeners that the call was disconnected before being added to CallsManager.
+        // Listeners will not receive onAdded or onRemoved callbacks.
+        if (!mCalls.contains(call)) {
+            if (call.isEmergencyCall()) {
+                mAnomalyReporter.reportAnomaly(
+                        EMERGENCY_CALL_DISCONNECTED_BEFORE_BEING_ADDED_ERROR_UUID,
+                        EMERGENCY_CALL_DISCONNECTED_BEFORE_BEING_ADDED_ERROR_MSG);
+            }
+            mListeners.forEach(l -> l.onCallCreatedButNeverAdded(call));
+        }
+
         // If a call diagnostic service is in use, we will log the original telephony-provided
         // disconnect cause, inform the CDS of the disconnection, and then chain the update of the
         // call state until AFTER the CDS reports it's result back.
@@ -3932,7 +3978,11 @@
                 connectElapsedTime,
                 mClockProxy,
                 mToastFactory);
-        notifyCallCreated(call);
+
+        // Unlike connections, conferences are not created first and then notified as create
+        // connection complete from the CS.  They originate from the CS and are reported directly to
+        // telecom where they're added (see below).
+        call.setIsCreateConnectionComplete(true);
 
         setCallState(call, Call.getStateFromConnectionState(parcelableConference.getState()),
                 "new conference call");
@@ -4903,17 +4953,28 @@
         }
     }
 
+    /**
+     * Ensures that the call will be audible to the user by checking if the voice call stream is
+     * audible, and if not increasing the volume to the default value.
+     */
     private void ensureCallAudible() {
-        AudioManager am = mContext.getSystemService(AudioManager.class);
-        if (am == null) {
-            Log.w(this, "ensureCallAudible: audio manager is null");
-            return;
-        }
-        if (am.getStreamVolume(AudioManager.STREAM_VOICE_CALL) == 0) {
-            Log.i(this, "ensureCallAudible: voice call stream has volume 0. Adjusting to default.");
-            am.setStreamVolume(AudioManager.STREAM_VOICE_CALL,
-                    AudioSystem.getDefaultStreamVolume(AudioManager.STREAM_VOICE_CALL), 0);
-        }
+        // Audio manager APIs can be somewhat slow.  To prevent a potential ANR we will fire off
+        // this opreation on the async task executor.  Note that this operation does not have any
+        // dependency on any Telecom state, so we can safely launch this on a different thread
+        // without worrying that it is in the Telecom sync lock.
+        mAsyncTaskExecutor.execute(() -> {
+            AudioManager am = mContext.getSystemService(AudioManager.class);
+            if (am == null) {
+                Log.w(this, "ensureCallAudible: audio manager is null");
+                return;
+            }
+            if (am.getStreamVolume(AudioManager.STREAM_VOICE_CALL) == 0) {
+                Log.i(this,
+                        "ensureCallAudible: voice call stream has volume 0. Adjusting to default.");
+                am.setStreamVolume(AudioManager.STREAM_VOICE_CALL,
+                        AudioSystem.getDefaultStreamVolume(AudioManager.STREAM_VOICE_CALL), 0);
+            }
+        });
     }
 
     /**
@@ -4980,6 +5041,9 @@
                 call.setParentCall(parentCall);
             }
         }
+        // Existing connections originate from a connection service, so they are completed creation
+        // by the ConnectionService implicitly.
+        call.setIsCreateConnectionComplete(true);
         addCall(call);
         if (parentCall != null) {
             // Now, set the call as a child of the parent since it has been added to Telecom.  This
@@ -5459,6 +5523,9 @@
         } else {
             call.setConnectionService(service);
             service.createConnectionFailed(call);
+            if (!mCalls.contains(call)){
+                mListeners.forEach(l -> l.onCallCreatedButNeverAdded(call));
+            }
         }
     }
 
@@ -5481,6 +5548,9 @@
         } else {
             call.setConnectionService(service);
             service.createConferenceFailed(call);
+            if (!mCalls.contains(call)){
+                mListeners.forEach(l -> l.onCallCreatedButNeverAdded(call));
+            }
         }
     }
 
@@ -5951,18 +6021,24 @@
         }
     }
 
-    // driver method to create and execute a new TransactionalFocusRequestCallback
-    public void transactionRequestNewFocusCall(Call call,
+    /**
+     * Intended for ongoing or new calls that would like to go active/answered and need to
+     * update the mConnectionSvrFocusMgr before setting the state
+     */
+    public void transactionRequestNewFocusCall(Call call, int newCallState,
             OutcomeReceiver<Boolean, CallException> callback) {
         Log.d(this, "transactionRequestNewFocusCall");
-        PendingAction pendingAction = new ActionSetCallState(call, CallState.ACTIVE,
+        PendingAction pendingAction = new ActionSetCallState(call, newCallState,
                 "transactional ActionSetCallState");
         mConnectionSvrFocusMgr
                 .requestFocus(call,
                         new TransactionalFocusRequestCallback(pendingAction, call, callback));
     }
 
-    // request a new call focus and ensure the request was successful
+    /**
+     * Request a new call focus and ensure the request was successful via an OutcomeReceiver. Also,
+     * include a PendingAction that will execute if the call focus change is successful.
+     */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
     public class TransactionalFocusRequestCallback implements
             ConnectionServiceFocusManager.RequestFocusCallback {
@@ -5981,26 +6057,18 @@
         @Override
         public void onRequestFocusDone(ConnectionServiceFocusManager.CallFocus call) {
             Call currentCallFocus = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
-
-            if (currentCallFocus == null) {
-                Log.i(this, "TransactionalFocusRequestCallback: "
-                        + "currentCallFocus is null.");
-                mCallback.onError(new CallException("currentCallFocus is null",
+            // verify the update was successful before updating the state
+            Log.i(this, "tFRC: currentCallFocus=[%s], targetFocus=[%s]",
+                    mTargetCallFocus, currentCallFocus);
+            if (currentCallFocus == null ||
+                    !currentCallFocus.getId().equals(mTargetCallFocus.getId())) {
+                mCallback.onError(new CallException("failed to switch focus to requested call",
                         CallException.CODE_CALL_CANNOT_BE_SET_TO_ACTIVE));
                 return;
             }
-
-            Log.i(this, "TransactionalFocusRequestCallback: targetId=[%s], "
-                    + "currentId=[%s]", mTargetCallFocus.getId(), currentCallFocus.getId());
-            if (!currentCallFocus.getId().equals(mTargetCallFocus.getId())) {
-                Log.i(this, "TransactionalFocusRequestCallback: "
-                        + "currentCallFocus is not equal to targetCallFocus.");
-                mCallback.onError(new CallException("current focus is not target focus",
-                        CallException.CODE_CALL_CANNOT_BE_SET_TO_ACTIVE));
-                return;
-            }
-            mPendingAction.performAction();
-            mCallback.onResult(true);
+            // at this point, we know the FocusManager is able to update successfully
+            mPendingAction.performAction(); // set the call state
+            mCallback.onResult(true); // complete the transaction
         }
     }
 
diff --git a/src/com/android/server/telecom/CallsManagerListenerBase.java b/src/com/android/server/telecom/CallsManagerListenerBase.java
index 6b8e2fe..6754237 100644
--- a/src/com/android/server/telecom/CallsManagerListenerBase.java
+++ b/src/com/android/server/telecom/CallsManagerListenerBase.java
@@ -35,6 +35,10 @@
     }
 
     @Override
+    public void onCallCreatedButNeverAdded(Call call) {
+    }
+
+    @Override
     public void onCallStateChanged(Call call, int oldState, int newState) {
     }
 
diff --git a/src/com/android/server/telecom/CarModeTracker.java b/src/com/android/server/telecom/CarModeTracker.java
index 737ce5a..ae8febf 100644
--- a/src/com/android/server/telecom/CarModeTracker.java
+++ b/src/com/android/server/telecom/CarModeTracker.java
@@ -150,7 +150,9 @@
         Log.i(this, "handleExitCarMode: packageName=%s, priority=%d", packageName, priority);
         mCarModeChangeLog.log("exitCarMode: packageName=" + packageName + ", priority="
                 + priority);
-        mCarModeApps.removeIf(c -> c.getPriority() == priority);
+
+        //Remove the car mode app with specified priority without clearing out the projection entry.
+        mCarModeApps.removeIf(c -> c.getPriority() == priority && !c.hasSetAutomotiveProjection());
     }
 
     public void handleSetAutomotiveProjection(@NonNull String packageName) {
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index 874b77f..59a84f9 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -1597,7 +1597,7 @@
             public void onSuccess() {
                 String callId = mCallIdMapper.getCallId(call);
                 if (callId == null) {
-                    Log.w(ConnectionServiceWrapper.this, "Call not present"
+                    Log.i(ConnectionServiceWrapper.this, "Call not present"
                             + " in call id mapper, maybe it was aborted before the bind"
                             + " completed successfully?");
                     response.handleCreateConnectionFailure(
diff --git a/src/com/android/server/telecom/HeadsetMediaButton.java b/src/com/android/server/telecom/HeadsetMediaButton.java
index 7458f54..8e9caff 100644
--- a/src/com/android/server/telecom/HeadsetMediaButton.java
+++ b/src/com/android/server/telecom/HeadsetMediaButton.java
@@ -23,11 +23,16 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.telecom.CallAudioState;
+import android.telecom.CallEndpoint;
 import android.telecom.Log;
+import android.util.ArraySet;
 import android.view.KeyEvent;
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.util.Set;
+
 /**
  * Static class to handle listening to the headset media buttons.
  */
@@ -149,8 +154,10 @@
     private final Context mContext;
     private final CallsManager mCallsManager;
     private final TelecomSystem.SyncRoot mLock;
+    private final Set<Call> mCalls = new ArraySet<>();
     private MediaSessionAdapter mSession;
     private KeyEvent mLastHookEvent;
+    private @CallEndpoint.EndpointType int mCurrentEndpointType;
 
     /**
      * Constructor used for testing purposes to initialize a {@link HeadsetMediaButton} with a
@@ -212,7 +219,7 @@
             return mCallsManager.onMediaButton(LONG_PRESS);
         } else if (event.getAction() == KeyEvent.ACTION_UP) {
             // We should not judge SHORT_PRESS by ACTION_UP event repeatCount, because it always
-            // return 0.
+            // returns 0.
             // Actually ACTION_DOWN event repeatCount only increases when LONG_PRESS performed.
             if (mLastHookEvent != null && mLastHookEvent.getRepeatCount() == 0) {
                 return mCallsManager.onMediaButton(SHORT_PRESS);
@@ -226,52 +233,72 @@
         return true;
     }
 
+    @Override
+    public void onCallEndpointChanged(CallEndpoint callEndpoint) {
+        mCurrentEndpointType = callEndpoint.getEndpointType();
+        Log.i(this, "onCallEndpointChanged: endPoint=%s", callEndpoint);
+        maybeChangeSessionState();
+    }
+
     /** ${inheritDoc} */
     @Override
     public void onCallAdded(Call call) {
-        if (call.isExternalCall()) {
-            return;
-        }
-        handleCallAddition();
+        handleCallAddition(call);
     }
 
     /**
      * Triggers session activation due to call addition.
      */
-    private void handleCallAddition() {
-        mMediaSessionHandler.obtainMessage(MSG_MEDIA_SESSION_SET_ACTIVE, 1, 0).sendToTarget();
-    }
-
-    /** ${inheritDoc} */
-    @Override
-    public void onCallRemoved(Call call) {
-        if (call.isExternalCall()) {
-            return;
-        }
-        handleCallRemoval();
+    private void handleCallAddition(Call call) {
+        mCalls.add(call);
+        maybeChangeSessionState();
     }
 
     /**
-     * Triggers session deactivation due to call removal.
+     * Based on whether there are tracked calls and the audio is routed to a wired headset,
+     * potentially activate or deactive the media session.
      */
-    private void handleCallRemoval() {
-        if (!mCallsManager.hasAnyCalls()) {
+    private void maybeChangeSessionState() {
+        boolean hasNonExternalCalls = !mCalls.isEmpty()
+                && mCalls.stream().anyMatch(c -> !c.isExternalCall());
+        if (hasNonExternalCalls && mCurrentEndpointType == CallEndpoint.TYPE_WIRED_HEADSET) {
+            Log.i(this, "maybeChangeSessionState: hasCalls=%b, currentEndpointType=%s, ACTIVATE",
+                    hasNonExternalCalls, CallEndpoint.endpointTypeToString(mCurrentEndpointType));
+            mMediaSessionHandler.obtainMessage(MSG_MEDIA_SESSION_SET_ACTIVE, 1, 0).sendToTarget();
+        } else {
+            Log.i(this, "maybeChangeSessionState: hasCalls=%b, currentEndpointType=%s, DEACTIVATE",
+                    hasNonExternalCalls, CallEndpoint.endpointTypeToString(mCurrentEndpointType));
             mMediaSessionHandler.obtainMessage(MSG_MEDIA_SESSION_SET_ACTIVE, 0, 0).sendToTarget();
         }
     }
 
     /** ${inheritDoc} */
     @Override
-    public void onExternalCallChanged(Call call, boolean isExternalCall) {
-        // Note: We don't use the onCallAdded/onCallRemoved methods here since they do checks to see
-        // if the call is external or not and would skip the session activation/deactivation.
-        if (isExternalCall) {
-            handleCallRemoval();
-        } else {
-            handleCallAddition();
+    public void onCallRemoved(Call call) {
+        handleCallRemoval(call);
+    }
+
+    /**
+     * Triggers session deactivation due to call removal.
+     */
+    private void handleCallRemoval(Call call) {
+        // If we were tracking the call, potentially change session state.
+        if (mCalls.remove(call)) {
+            if (mCalls.isEmpty()) {
+                // When there are no calls, don't cache that we previously had a wired headset
+                // connected; we'll be updated on the next call.
+                mCurrentEndpointType = CallEndpoint.TYPE_UNKNOWN;
+            }
+            maybeChangeSessionState();
         }
     }
 
+    /** ${inheritDoc} */
+    @Override
+    public void onExternalCallChanged(Call call, boolean isExternalCall) {
+        maybeChangeSessionState();
+    }
+
     @VisibleForTesting
     /**
      * @return the handler this class instance uses for operation; used for unit testing.
diff --git a/src/com/android/server/telecom/InCallAdapter.java b/src/com/android/server/telecom/InCallAdapter.java
index f5214ed..9ce10bd 100755
--- a/src/com/android/server/telecom/InCallAdapter.java
+++ b/src/com/android/server/telecom/InCallAdapter.java
@@ -695,7 +695,7 @@
     @Override
     public void sendRttRequest(String callId) {
         try {
-            Log.startSession("ICA.sRR");
+            Log.startSession("ICA.sRR", mOwnerPackageAbbreviation);
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
@@ -717,7 +717,7 @@
     @Override
     public void respondToRttRequest(String callId, int id, boolean accept) {
         try {
-            Log.startSession("ICA.rTRR");
+            Log.startSession("ICA.rTRR", mOwnerPackageAbbreviation);
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
@@ -739,7 +739,7 @@
     @Override
     public void stopRtt(String callId) {
         try {
-            Log.startSession("ICA.sRTT");
+            Log.startSession("ICA.sRTT", mOwnerPackageAbbreviation);
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
@@ -761,11 +761,16 @@
     @Override
     public void setRttMode(String callId, int mode) {
         try {
-            Log.startSession("ICA.sRM");
+            Log.startSession("ICA.sRM", mOwnerPackageAbbreviation);
             long token = Binder.clearCallingIdentity();
             try {
                 synchronized (mLock) {
-                    // TODO
+                    Call call = mCallIdMapper.getCall(callId);
+                    if (call != null) {
+                        call.setRttMode(mode);
+                    } else {
+                        Log.w(this, "setRttMode(): call %s not found", callId);
+                    }
                 }
             } finally {
                 Binder.restoreCallingIdentity(token);
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 33ddf0a..01eb319 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -2600,8 +2600,17 @@
 
     private UserHandle getUserFromCall(Call call) {
         // Call may never be specified, so we can fall back to using the CallManager current user.
-        return call == null
-                ? mCallsManager.getCurrentUserHandle()
-                : call.getUserHandleFromTargetPhoneAccount();
+        if (call == null) {
+            return mCallsManager.getCurrentUserHandle();
+        } else {
+            UserHandle userFromCall = call.getUserHandleFromTargetPhoneAccount();
+            UserManager userManager = mContext.getSystemService(UserManager.class);
+            // Emergency call should never be blocked, so if the user associated with call is in
+            // quite mode, use the primary user for the emergency call.
+            if (call.isEmergencyCall() && userManager.isQuietModeEnabled(userFromCall)) {
+                return mCallsManager.getCurrentUserHandle();
+            }
+            return userFromCall;
+        }
     }
 }
diff --git a/src/com/android/server/telecom/InCallTonePlayer.java b/src/com/android/server/telecom/InCallTonePlayer.java
index 0957eb4..3cc4aac 100644
--- a/src/com/android/server/telecom/InCallTonePlayer.java
+++ b/src/com/android/server/telecom/InCallTonePlayer.java
@@ -170,7 +170,7 @@
 
     private static final int RELATIVE_VOLUME_EMERGENCY = 100;
     private static final int RELATIVE_VOLUME_HIPRI = 80;
-    private static final int RELATIVE_VOLUME_LOPRI = 50;
+    private static final int RELATIVE_VOLUME_LOPRI = 30;
     private static final int RELATIVE_VOLUME_UNDEFINED = -1;
 
     // Buffer time (in msec) to add on to the tone timeout value. Needed mainly when the timeout
diff --git a/src/com/android/server/telecom/LogUtils.java b/src/com/android/server/telecom/LogUtils.java
index 35f8d1e..f233ccb 100644
--- a/src/com/android/server/telecom/LogUtils.java
+++ b/src/com/android/server/telecom/LogUtils.java
@@ -109,6 +109,11 @@
         public static final String SET_SELECT_PHONE_ACCOUNT = "SET_SELECT_PHONE_ACCOUNT";
         public static final String SET_AUDIO_PROCESSING = "SET_AUDIO_PROCESSING";
         public static final String SET_SIMULATED_RINGING = "SET_SIMULATED_RINGING";
+        public static final String REQUEST_RTT = "REQUEST_RTT";
+        public static final String RESPOND_TO_RTT_REQUEST = "RESPOND_TO_RTT_REQUEST";
+        public static final String SET_RRT_MODE = "SET_RTT_MODE";
+        public static final String ON_RTT_FAILED = "ON_RTT_FAILED";
+        public static final String ON_RTT_REQUEST = "ON_RTT_REQUEST";
         public static final String REQUEST_HOLD = "REQUEST_HOLD";
         public static final String REQUEST_UNHOLD = "REQUEST_UNHOLD";
         public static final String REQUEST_DISCONNECT = "REQUEST_DISCONNECT";
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index a7824b4..3c6934a 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -962,18 +962,20 @@
 
         String[] fields =
                 {"Package Name", "Class Name", "PhoneAccountHandle Id", "Label", "ShortDescription",
-                        "GroupId", "Address"};
+                        "GroupId", "Address", "SubscriptionAddress"};
         CharSequence[] args = {handle.getComponentName().getPackageName(),
                 handle.getComponentName().getClassName(), handle.getId(), account.getLabel(),
                 account.getShortDescription(), account.getGroupId(),
-                (account.getAddress() != null ? account.getAddress().toString() : "")};
+                (account.getAddress() != null ? account.getAddress().toString() : ""),
+                (account.getSubscriptionAddress() != null ?
+                        account.getSubscriptionAddress().toString() : "")};
 
         for (int i = 0; i < fields.length; i++) {
             if (args[i] != null && args[i].length() > MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT) {
                 EventLog.writeEvent(0x534e4554, "259064622", Binder.getCallingUid(),
                         "enforceCharacterLimit");
-                throw new IllegalArgumentException("The PhoneAccount or PhoneAccountHandle"
-                        + fields[i] + " field has an invalid character count. PhoneAccount and "
+                throw new IllegalArgumentException("The PhoneAccount or PhoneAccountHandle ["
+                        + fields[i] + "] field has an invalid character count. PhoneAccount and "
                         + "PhoneAccountHandle String and Char-Sequence fields are limited to "
                         + MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT + " characters.");
             }
diff --git a/src/com/android/server/telecom/Ringer.java b/src/com/android/server/telecom/Ringer.java
index 171745f..3b45355 100644
--- a/src/com/android/server/telecom/Ringer.java
+++ b/src/com/android/server/telecom/Ringer.java
@@ -21,6 +21,7 @@
 import static android.provider.CallLog.Calls.USER_MISSED_NO_VIBRATE;
 import static android.provider.Settings.Global.ZEN_MODE_OFF;
 
+import android.annotation.NonNull;
 import android.app.Notification;
 import android.app.NotificationManager;
 import android.app.Person;
@@ -32,6 +33,8 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.UserHandle;
+import android.os.UserManager;
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
@@ -54,20 +57,25 @@
  */
 @VisibleForTesting
 public class Ringer {
-  /**
-   * Flag only for local debugging. Do not submit enabled.
-   */
-  private static final boolean DEBUG_RINGER = false;
-
-  public static class VibrationEffectProxy {
-    public VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
-      return VibrationEffect.createWaveform(timings, amplitudes, repeat);
+    public interface AccessibilityManagerAdapter {
+        boolean startFlashNotificationSequence(@NonNull Context context,
+                @AccessibilityManager.FlashNotificationReason int reason);
+        boolean stopFlashNotificationSequence(@NonNull Context context);
     }
+    /**
+     * Flag only for local debugging. Do not submit enabled.
+     */
+    private static final boolean DEBUG_RINGER = false;
 
-    public VibrationEffect get(Uri ringtoneUri, Context context) {
-      return VibrationEffect.get(ringtoneUri, context);
+    public static class VibrationEffectProxy {
+        public VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
+            return VibrationEffect.createWaveform(timings, amplitudes, repeat);
+        }
+
+        public VibrationEffect get(Uri ringtoneUri, Context context) {
+            return VibrationEffect.get(ringtoneUri, context);
+        }
     }
-  }
     @VisibleForTesting
     public VibrationEffect mDefaultVibrationEffect;
 
@@ -162,7 +170,7 @@
     private RingtoneFactory mRingtoneFactory;
     private AudioManager mAudioManager;
     private NotificationManager mNotificationManager;
-    private AccessibilityManager mAccessibilityManager;
+    private AccessibilityManagerAdapter mAccessibilityManagerAdapter;
 
     /**
      * Call objects that are ringing, vibrating or call-waiting. These are used only for logging
@@ -197,7 +205,7 @@
             VibrationEffectProxy vibrationEffectProxy,
             InCallController inCallController,
             NotificationManager notificationManager,
-            AccessibilityManager accessibilityManager) {
+            AccessibilityManagerAdapter accessibilityManagerAdapter) {
 
         mLock = new Object();
         mSystemSettingsUtil = systemSettingsUtil;
@@ -211,7 +219,7 @@
         mInCallController = inCallController;
         mVibrationEffectProxy = vibrationEffectProxy;
         mNotificationManager = notificationManager;
-        mAccessibilityManager = accessibilityManager;
+        mAccessibilityManagerAdapter = accessibilityManagerAdapter;
 
         if (mContext.getResources().getBoolean(R.bool.use_simple_vibration_pattern)) {
             mDefaultVibrationEffect = mVibrationEffectProxy.createWaveform(SIMPLE_VIBRATION_PATTERN,
@@ -286,6 +294,10 @@
                 Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Silent ringing "
                         + "requested");
             }
+            if (attributes.isWorkProfileInQuietMode()) {
+                Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING,
+                        "Work profile in quiet mode");
+            }
             if (mBlockOnRingingFuture != null) {
                 mBlockOnRingingFuture.complete(null);
             }
@@ -295,10 +307,11 @@
         stopCallWaiting();
 
         final boolean shouldFlash = attributes.shouldRingForContact();
-        if (mAccessibilityManager != null && shouldFlash) {
+        if (mAccessibilityManagerAdapter != null && shouldFlash) {
             Log.addEvent(foregroundCall, LogUtils.Events.FLASH_NOTIFICATION_START);
-            getHandler().post(() -> mAccessibilityManager.startFlashNotificationSequence(mContext,
-                    AccessibilityManager.FLASH_REASON_CALL));
+            getHandler().post(() ->
+                    mAccessibilityManagerAdapter.startFlashNotificationSequence(mContext,
+                        AccessibilityManager.FLASH_REASON_CALL));
         }
 
         // Determine if the settings and DND mode indicate that the vibrator can be used right now.
@@ -512,9 +525,10 @@
 
     public void stopRinging() {
         final Call foregroundCall = mRingingCall != null ? mRingingCall : mVibratingCall;
-        if (mAccessibilityManager != null) {
+        if (mAccessibilityManagerAdapter != null) {
             Log.addEvent(foregroundCall, LogUtils.Events.FLASH_NOTIFICATION_STOP);
-            getHandler().post(() -> mAccessibilityManager.stopFlashNotificationSequence(mContext));
+            getHandler().post(() ->
+                    mAccessibilityManagerAdapter.stopFlashNotificationSequence(mContext));
         }
 
         synchronized (mLock) {
@@ -632,16 +646,20 @@
         boolean letDialerHandleRinging = mInCallController.doesConnectedDialerSupportRinging(
                 call.getUserHandleFromTargetPhoneAccount());
         timer.record("letDialerHandleRinging");
+        boolean isWorkProfileInQuietMode =
+                isProfileInQuietMode(call.getUserHandleFromTargetPhoneAccount());
+        timer.record("isWorkProfileInQuietMode");
 
         Log.i(this, "startRinging timings: " + timer);
         boolean endEarly = isTheaterModeOn || letDialerHandleRinging || isSelfManaged ||
-                hasExternalRinger || isSilentRingingRequested;
+                hasExternalRinger || isSilentRingingRequested || isWorkProfileInQuietMode;
 
         if (endEarly) {
             Log.i(this, "Ending early -- isTheaterModeOn=%s, letDialerHandleRinging=%s, " +
-                            "isSelfManaged=%s, hasExternalRinger=%s, silentRingingRequested=%s",
+                            "isSelfManaged=%s, hasExternalRinger=%s, silentRingingRequested=%s, " +
+                            "isWorkProfileInQuietMode=%s",
                     isTheaterModeOn, letDialerHandleRinging, isSelfManaged, hasExternalRinger,
-                    isSilentRingingRequested);
+                    isSilentRingingRequested, isWorkProfileInQuietMode);
         }
 
         // Acquire audio focus under any of the following conditions:
@@ -649,8 +667,8 @@
         // 2. Volume is over zero, we should ring for the contact, and there's a audible ringtone
         //    present. (This check is deferred until ringer knows the ringtone)
         // 3. The call is self-managed.
-        boolean shouldAcquireAudioFocus =
-            (isHfpDeviceAttached && shouldRingForContact) || isSelfManaged;
+        boolean shouldAcquireAudioFocus = !isWorkProfileInQuietMode &&
+                ((isHfpDeviceAttached && shouldRingForContact) || isSelfManaged);
 
         // Set missed reason according to attributes
         if (!isVolumeOverZero) {
@@ -668,9 +686,15 @@
                 .setInaudibleReason(inaudibleReason)
                 .setShouldRingForContact(shouldRingForContact)
                 .setSilentRingingRequested(isSilentRingingRequested)
+                .setWorkProfileQuietMode(isWorkProfileInQuietMode)
                 .build();
     }
 
+    private boolean isProfileInQuietMode(UserHandle user) {
+        UserManager um = mContext.getSystemService(UserManager.class);
+        return um.isManagedProfile(user.getIdentifier()) && um.isQuietModeEnabled(user);
+    }
+
     private Handler getHandler() {
         if (mHandler == null) {
             HandlerThread handlerThread = new HandlerThread("Ringer");
diff --git a/src/com/android/server/telecom/RingerAttributes.java b/src/com/android/server/telecom/RingerAttributes.java
index 840d815..e0d3e1c 100644
--- a/src/com/android/server/telecom/RingerAttributes.java
+++ b/src/com/android/server/telecom/RingerAttributes.java
@@ -25,6 +25,7 @@
         private String mInaudibleReason;
         private boolean mShouldRingForContact;
         private boolean mSilentRingingRequested;
+        private boolean mWorkProfileQuietMode;
 
         public RingerAttributes.Builder setEndEarly(boolean endEarly) {
             mEndEarly = endEarly;
@@ -61,10 +62,15 @@
             return this;
         }
 
+        public RingerAttributes.Builder setWorkProfileQuietMode(boolean workProfileQuietMode) {
+            mWorkProfileQuietMode = workProfileQuietMode;
+            return this;
+        }
+
         public RingerAttributes build() {
             return new RingerAttributes(mEndEarly, mLetDialerHandleRinging, mAcquireAudioFocus,
                     mRingerAudible, mInaudibleReason, mShouldRingForContact,
-                    mSilentRingingRequested);
+                    mSilentRingingRequested, mWorkProfileQuietMode);
         }
     }
 
@@ -75,10 +81,12 @@
     private String mInaudibleReason;
     private boolean mShouldRingForContact;
     private boolean mSilentRingingRequested;
+    private boolean mWorkProfileQuietMode;
 
     private RingerAttributes(boolean endEarly, boolean letDialerHandleRinging,
             boolean acquireAudioFocus, boolean ringerAudible, String inaudibleReason,
-            boolean shouldRingForContact, boolean silentRingingRequested) {
+            boolean shouldRingForContact, boolean silentRingingRequested,
+            boolean workProfileQuietMode) {
         mEndEarly = endEarly;
         mLetDialerHandleRinging = letDialerHandleRinging;
         mAcquireAudioFocus = acquireAudioFocus;
@@ -86,6 +94,7 @@
         mInaudibleReason = inaudibleReason;
         mShouldRingForContact = shouldRingForContact;
         mSilentRingingRequested = silentRingingRequested;
+        mWorkProfileQuietMode = workProfileQuietMode;
     }
 
     public boolean isEndEarly() {
@@ -115,4 +124,8 @@
     public boolean isSilentRingingRequested() {
         return mSilentRingingRequested;
     }
+
+    public boolean isWorkProfileInQuietMode() {
+        return mWorkProfileQuietMode;
+    }
 }
diff --git a/src/com/android/server/telecom/StatusBarNotifier.java b/src/com/android/server/telecom/StatusBarNotifier.java
index d1de958..772335e 100644
--- a/src/com/android/server/telecom/StatusBarNotifier.java
+++ b/src/com/android/server/telecom/StatusBarNotifier.java
@@ -79,13 +79,15 @@
         mIsShowingMute = isMuted;
     }
 
+    /**
+     * Update the status bar manager with the new speakerphone state.
+     *
+     * IMPORTANT: DO NOT call into any Telecom code here; this is usually scheduled on an async
+     * executor to save Telecom from blocking on outgoing binder calls.
+     * @param isSpeakerphone
+     */
     @VisibleForTesting
     public void notifySpeakerphone(boolean isSpeakerphone) {
-        // Never display anything if there are no calls.
-        if (!mCallsManager.hasAnyCalls()) {
-            isSpeakerphone = false;
-        }
-
         if (mIsShowingSpeakerphone == isSpeakerphone) {
             return;
         }
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 6826290..90e7164 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -1813,7 +1813,7 @@
                         }
                         mUserCallIntentProcessorFactory.create(mContext, userHandle)
                                 .processIntent(
-                                        intent, callingPackage, isSelfManaged ||
+                                        intent, callingPackage, isSelfManaged,
                                                 (hasCallAppOp && hasCallPermission),
                                         true /* isLocalInvocation */);
                     } finally {
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index 63852f3..8354d5e 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -31,6 +31,7 @@
 import android.telecom.Log;
 import android.telecom.PhoneAccountHandle;
 import android.telephony.AnomalyReporter;
+import android.view.accessibility.AccessibilityManager;
 import android.widget.Toast;
 
 import androidx.annotation.NonNull;
@@ -52,6 +53,7 @@
 import java.io.FileNotFoundException;
 import java.io.InputStream;
 import java.util.List;
+import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 
 /**
@@ -208,7 +210,9 @@
             ClockProxy clockProxy,
             RoleManagerAdapter roleManagerAdapter,
             ContactsAsyncHelper.Factory contactsAsyncHelperFactory,
-            DeviceIdleControllerAdapter deviceIdleControllerAdapter) {
+            DeviceIdleControllerAdapter deviceIdleControllerAdapter,
+            Ringer.AccessibilityManagerAdapter accessibilityManagerAdapter,
+            Executor asyncTaskExecutor) {
         mContext = context.getApplicationContext();
         LogUtils.initLogging(mContext);
         AnomalyReporter.initialize(mContext);
@@ -332,6 +336,7 @@
             CallAnomalyWatchdog callAnomalyWatchdog = new CallAnomalyWatchdog(
                     Executors.newSingleThreadScheduledExecutor(),
                     mLock, timeoutsAdapter, clockProxy);
+
             mCallsManager = new CallsManager(
                     mContext,
                     mLock,
@@ -363,7 +368,9 @@
                     roleManagerAdapter,
                     toastFactory,
                     callEndpointControllerFactory,
-                    callAnomalyWatchdog);
+                    callAnomalyWatchdog,
+                    accessibilityManagerAdapter,
+                    asyncTaskExecutor);
 
             mIncomingCallNotifier = incomingCallNotifier;
             incomingCallNotifier.setCallsManagerProxy(new IncomingCallNotifier.CallsManagerProxy() {
diff --git a/src/com/android/server/telecom/Timeouts.java b/src/com/android/server/telecom/Timeouts.java
index d4713e8..39c86ad 100644
--- a/src/com/android/server/telecom/Timeouts.java
+++ b/src/com/android/server/telecom/Timeouts.java
@@ -392,7 +392,7 @@
      */
     public static long getNonVoipCallIntermediateStateTimeoutMillis() {
         return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
-                INTERMEDIATE_STATE_NON_VOIP_NORMAL_TIMEOUT_MILLIS, 60000L);
+                INTERMEDIATE_STATE_NON_VOIP_NORMAL_TIMEOUT_MILLIS, 120000L);
     }
 
     /**
diff --git a/src/com/android/server/telecom/TransactionalServiceWrapper.java b/src/com/android/server/telecom/TransactionalServiceWrapper.java
index 6931996..d97d192 100644
--- a/src/com/android/server/telecom/TransactionalServiceWrapper.java
+++ b/src/com/android/server/telecom/TransactionalServiceWrapper.java
@@ -38,6 +38,7 @@
 
 import com.android.internal.telecom.ICallControl;
 import com.android.internal.telecom.ICallEventCallback;
+import com.android.server.telecom.voip.AnswerCallTransaction;
 import com.android.server.telecom.voip.CallEventCallbackAckTransaction;
 import com.android.server.telecom.voip.EndpointChangeTransaction;
 import com.android.server.telecom.voip.HoldCallTransaction;
@@ -67,7 +68,7 @@
     // CallControl : Client (ex. voip app) --> Telecom
     public static final String SET_ACTIVE = "SetActive";
     public static final String SET_INACTIVE = "SetInactive";
-    public static final String REJECT = "Reject";
+    public static final String ANSWER = "Answer";
     public static final String DISCONNECT = "Disconnect";
     public static final String START_STREAMING = "StartStreaming";
 
@@ -75,7 +76,6 @@
     public static final String ON_SET_ACTIVE = "onSetActive";
     public static final String ON_SET_INACTIVE = "onSetInactive";
     public static final String ON_ANSWER = "onAnswer";
-    public static final String ON_REJECT = "onReject";
     public static final String ON_DISCONNECT = "onDisconnect";
     public static final String ON_STREAMING_STARTED = "onStreamingStarted";
 
@@ -196,6 +196,17 @@
         }
 
         @Override
+        public void answer(int videoState, String callId, android.os.ResultReceiver callback)
+                throws RemoteException {
+            try {
+                Log.startSession("TSW.a");
+                createTransactions(callId, callback, ANSWER, videoState);
+            } finally {
+                Log.endSession();
+            }
+        }
+
+        @Override
         public void setInactive(String callId, android.os.ResultReceiver callback)
                 throws RemoteException {
             try {
@@ -238,6 +249,10 @@
                     case SET_ACTIVE:
                         addTransactionsToManager(createSetActiveTransactions(call), callback);
                         break;
+                    case ANSWER:
+                        addTransactionsToManager(createSetAnswerTransactions(call,
+                                (int) objects[0]), callback);
+                        break;
                     case DISCONNECT:
                         addTransactionsToManager(new EndCallTransaction(mCallsManager,
                                 (DisconnectCause) objects[0], call), callback);
@@ -273,6 +288,28 @@
                 Log.endSession();
             }
         }
+
+        /**
+         * Application would like to inform InCallServices of an event
+         */
+        @Override
+        public void sendEvent(String callId, String event, Bundle extras) {
+            try {
+                Log.startSession("TSW.sE");
+                Call call = mTrackedCalls.get(callId);
+                if (call != null) {
+                    call.onConnectionEvent(event, extras);
+                }
+                else{
+                    Log.i(TAG,
+                            "sendEvent: was called but there is no call with id=[%s] cannot be "
+                                    + "found. Most likely the call has been disconnected");
+                }
+            }
+            finally {
+                Log.endSession();
+            }
+        }
     };
 
     private void addTransactionsToManager(VoipCallTransaction transaction,
@@ -366,7 +403,7 @@
             Log.i(TAG, String.format(Locale.US, "onSetInactive: callId=[%s]", call.getId()));
             mTransactionManager.addTransaction(
                     new CallEventCallbackAckTransaction(mICallEventCallback,
-                            ON_SET_INACTIVE, call.getId(), 0), new OutcomeReceiver<>() {
+                            ON_SET_INACTIVE, call.getId()), new OutcomeReceiver<>() {
                         @Override
                         public void onResult(VoipCallTransactionResult result) {
                             mCallsManager.markCallAsOnHold(call);
@@ -389,7 +426,7 @@
 
             mTransactionManager.addTransaction(
                     new CallEventCallbackAckTransaction(mICallEventCallback, ON_DISCONNECT,
-                            call.getId(), 0), new OutcomeReceiver<>() {
+                            call.getId(), cause), new OutcomeReceiver<>() {
                         @Override
                         public void onResult(VoipCallTransactionResult result) {
                             removeCallFromCallsManager(call, cause);
@@ -406,32 +443,6 @@
         }
     }
 
-    public void onReject(Call call, @android.telecom.Call.RejectReason int rejectReason) {
-        try {
-            Log.startSession("TSW.oR");
-            Log.d(TAG, String.format(Locale.US, "onReject: callId=[%s]", call.getId()));
-
-            mTransactionManager.addTransaction(
-                    new CallEventCallbackAckTransaction(mICallEventCallback, ON_REJECT,
-                            call.getId(), 0), new OutcomeReceiver<>() {
-                        @Override
-                        public void onResult(VoipCallTransactionResult result) {
-                            removeCallFromCallsManager(call,
-                                    new DisconnectCause(DisconnectCause.REJECTED));
-                        }
-
-                        @Override
-                        public void onError(CallException exception) {
-                            removeCallFromCallsManager(call,
-                                    new DisconnectCause(DisconnectCause.REJECTED));
-                        }
-                    }
-            );
-        } finally {
-            Log.endSession();
-        }
-    }
-
     public void onCallStreamingStarted(Call call) {
         try {
             Log.startSession("TSW.oCSS");
@@ -440,7 +451,7 @@
 
             mTransactionManager.addTransaction(
                     new CallEventCallbackAckTransaction(mICallEventCallback, ON_STREAMING_STARTED,
-                            call.getId(), 0), new OutcomeReceiver<>() {
+                            call.getId()), new OutcomeReceiver<>() {
                         @Override
                         public void onResult(VoipCallTransactionResult result) {
                         }
@@ -508,6 +519,15 @@
         }
     }
 
+    public void onEvent(Call call, String event, Bundle extras){
+        if (call != null) {
+            try {
+                mICallEventCallback.onEvent(call.getId(), event, extras);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
     /***
      *********************************************************************************************
      **                                Helpers                                                  **
@@ -536,13 +556,27 @@
         // add t1. hold potential active call
         transactions.add(new HoldActiveCallForNewCallTransaction(mCallsManager, call));
 
-        // add t2. answer current call
+        // add t2. send request to set the current call active
         transactions.add(new RequestFocusTransaction(mCallsManager, call));
 
         // send off to Transaction Manager to process
         return new SerialTransaction(transactions);
     }
 
+    private SerialTransaction createSetAnswerTransactions(Call call, int videoState) {
+        // create list for multiple transactions
+        List<VoipCallTransaction> transactions = new ArrayList<>();
+
+        // add t1. hold potential active call
+        transactions.add(new HoldActiveCallForNewCallTransaction(mCallsManager, call));
+
+        // add t2. answer current call
+        transactions.add(new AnswerCallTransaction(mCallsManager, call, videoState));
+
+        // send off to Transaction Manager to process
+        return new SerialTransaction(transactions);
+    }
+
     /***
      *********************************************************************************************
      **                    FocusManager                                                       **
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index 9ad0da4..2ae5d8a 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -29,6 +29,7 @@
 import android.telecom.Log;
 
 import android.telecom.CallerInfoAsyncQuery;
+import android.view.accessibility.AccessibilityManager;
 
 import com.android.internal.telecom.IInternalServiceRetriever;
 import com.android.internal.telecom.ITelecomLoader;
@@ -53,6 +54,7 @@
 import com.android.server.telecom.ProximitySensorManagerFactory;
 import com.android.server.telecom.InCallWakeLockController;
 import com.android.server.telecom.ProximitySensorManager;
+import com.android.server.telecom.Ringer;
 import com.android.server.telecom.RoleManagerAdapterImpl;
 import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.TelecomWakeLock;
@@ -61,6 +63,8 @@
 import com.android.server.telecom.ui.MissedCallNotifierImpl;
 import com.android.server.telecom.ui.NotificationChannelManager;
 
+import java.util.concurrent.Executors;
+
 /**
  * Implementation of the ITelecom interface.
  */
@@ -191,7 +195,23 @@
                             new RoleManagerAdapterImpl(context,
                                     (RoleManager) context.getSystemService(Context.ROLE_SERVICE)),
                             new ContactsAsyncHelper.Factory(),
-                            internalServiceRetriever.getDeviceIdleController()));
+                            internalServiceRetriever.getDeviceIdleController(),
+                            new Ringer.AccessibilityManagerAdapter() {
+                                @Override
+                                public boolean startFlashNotificationSequence(
+                                        @androidx.annotation.NonNull Context context, int reason) {
+                                    return context.getSystemService(AccessibilityManager.class)
+                                            .startFlashNotificationSequence(context, reason);
+                                }
+
+                                @Override
+                                public boolean stopFlashNotificationSequence(
+                                        @androidx.annotation.NonNull Context context) {
+                                    return context.getSystemService(AccessibilityManager.class)
+                                            .stopFlashNotificationSequence(context);
+                                }
+                            },
+                            Executors.newSingleThreadExecutor()));
         }
     }
 
diff --git a/src/com/android/server/telecom/components/UserCallActivity.java b/src/com/android/server/telecom/components/UserCallActivity.java
index 1d85884..d7b2001 100644
--- a/src/com/android/server/telecom/components/UserCallActivity.java
+++ b/src/com/android/server/telecom/components/UserCallActivity.java
@@ -74,7 +74,8 @@
             // ActivityThread.ActivityClientRecord#intent directly.
             // Modifying directly may be a potential risk when relaunching this activity.
             new UserCallIntentProcessor(this, userHandle).processIntent(new Intent(intent),
-                    getCallingPackage(), true /* hasCallAppOp*/, false /* isLocalInvocation */);
+                    getCallingPackage(), false, true /* hasCallAppOp*/,
+                    false /* isLocalInvocation */);
         } finally {
             Log.endSession();
             wakelock.release();
diff --git a/src/com/android/server/telecom/components/UserCallIntentProcessor.java b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
index cad7b4c..ca990bc 100755
--- a/src/com/android/server/telecom/components/UserCallIntentProcessor.java
+++ b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
@@ -69,6 +69,7 @@
      *
      * @param intent The intent.
      * @param callingPackageName The package name of the calling app.
+     * @param isSelfManaged      {@code true} if SelfManaged profile enabled.
      * @param canCallNonEmergency {@code true} if the caller is permitted to call non-emergency
      *                            numbers.
      * @param isLocalInvocation {@code true} if the caller is within the system service (i.e. the
@@ -79,19 +80,21 @@
      *                            service resides.
      */
     public void processIntent(Intent intent, String callingPackageName,
-            boolean canCallNonEmergency, boolean isLocalInvocation) {
+            boolean isSelfManaged, boolean canCallNonEmergency,
+            boolean isLocalInvocation) {
         String action = intent.getAction();
 
         if (Intent.ACTION_CALL.equals(action) ||
                 Intent.ACTION_CALL_PRIVILEGED.equals(action) ||
                 Intent.ACTION_CALL_EMERGENCY.equals(action)) {
-            processOutgoingCallIntent(intent, callingPackageName, canCallNonEmergency,
-                    isLocalInvocation);
+            processOutgoingCallIntent(intent, callingPackageName, isSelfManaged,
+                    canCallNonEmergency, isLocalInvocation);
         }
     }
 
     private void processOutgoingCallIntent(Intent intent, String callingPackageName,
-            boolean canCallNonEmergency, boolean isLocalInvocation) {
+            boolean isSelfManaged, boolean canCallNonEmergency,
+            boolean isLocalInvocation) {
         Uri handle = intent.getData();
         if (handle == null) return;
         String scheme = handle.getScheme();
@@ -102,35 +105,37 @@
             handle = Uri.fromParts(PhoneAccount.SCHEME_SIP, uriString, null);
         }
 
-        // Check DISALLOW_OUTGOING_CALLS restriction. Note: We are skipping this check in a managed
-        // profile user because this check can always be bypassed by copying and pasting the phone
-        // number into the personal dialer.
-        if (!UserUtil.isManagedProfile(mContext, mUserHandle)) {
-            // Only emergency calls are allowed for users with the DISALLOW_OUTGOING_CALLS
-            // restriction.
-            if (!TelephonyUtil.shouldProcessAsEmergency(mContext, handle)) {
-                final UserManager userManager = (UserManager) mContext.getSystemService(
-                        Context.USER_SERVICE);
-                if (userManager.hasBaseUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
-                        mUserHandle)) {
-                    showErrorDialogForRestrictedOutgoingCall(mContext,
-                            R.string.outgoing_call_not_allowed_user_restriction);
-                    Log.w(this, "Rejecting non-emergency phone call due to DISALLOW_OUTGOING_CALLS "
-                            + "restriction");
-                    return;
-                } else if (userManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
-                        mUserHandle)) {
-                    final DevicePolicyManager dpm =
-                            mContext.getSystemService(DevicePolicyManager.class);
-                    if (dpm == null) {
+       if(!isSelfManaged && !isLocalInvocation) {
+            // Check DISALLOW_OUTGOING_CALLS restriction. Note: We are skipping this
+            // check in a managed profile user because this check can always be bypassed
+            // by copying and pasting the phone number into the personal dialer.
+            if (!UserUtil.isManagedProfile(mContext, mUserHandle)) {
+                // Only emergency calls are allowed for users with the DISALLOW_OUTGOING_CALLS
+                // restriction.
+                if (!TelephonyUtil.shouldProcessAsEmergency(mContext, handle)) {
+                    final UserManager userManager =
+                            (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+                    if (userManager.hasBaseUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
+                            mUserHandle)) {
+                        showErrorDialogForRestrictedOutgoingCall(mContext,
+                                R.string.outgoing_call_not_allowed_user_restriction);
+                        Log.w(this, "Rejecting non-emergency phone call "
+                                + "due to DISALLOW_OUTGOING_CALLS restriction");
+                        return;
+                    } else if (userManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
+                            mUserHandle)) {
+                        final DevicePolicyManager dpm =
+                                mContext.getSystemService(DevicePolicyManager.class);
+                        if (dpm == null) {
+                            return;
+                        }
+                        final Intent adminSupportIntent = dpm.createAdminSupportIntent(
+                                UserManager.DISALLOW_OUTGOING_CALLS);
+                        if (adminSupportIntent != null) {
+                            mContext.startActivity(adminSupportIntent);
+                        }
                         return;
                     }
-                    final Intent adminSupportIntent = dpm.createAdminSupportIntent(
-                            UserManager.DISALLOW_OUTGOING_CALLS);
-                    if (adminSupportIntent != null) {
-                        mContext.startActivity(adminSupportIntent);
-                    }
-                    return;
                 }
             }
         }
diff --git a/src/com/android/server/telecom/voip/AnswerCallTransaction.java b/src/com/android/server/telecom/voip/AnswerCallTransaction.java
new file mode 100644
index 0000000..1eb34c4
--- /dev/null
+++ b/src/com/android/server/telecom/voip/AnswerCallTransaction.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom.voip;
+
+import android.os.OutcomeReceiver;
+import android.telecom.CallException;
+import android.util.Log;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.CallsManager;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+
+/**
+ * This transaction should be created for new incoming calls that request to go from
+ * CallState.Ringing to CallState.Answered.  Before changing the CallState, the focus manager must
+ * be updated. Once the focus manager updates, the call state will be set.  If there is an issue
+ * answering the call, the transaction will fail.
+ */
+public class AnswerCallTransaction extends VoipCallTransaction {
+
+    private static final String TAG = AnswerCallTransaction.class.getSimpleName();
+    private final CallsManager mCallsManager;
+    private final Call mCall;
+    private final int mVideoState;
+
+    public AnswerCallTransaction(CallsManager callsManager, Call call, int videoState) {
+        mCallsManager = callsManager;
+        mCall = call;
+        mVideoState = videoState;
+    }
+
+    @Override
+    public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+        Log.d(TAG, "processTransaction");
+        CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
+
+        mCall.setVideoState(mVideoState);
+
+        mCallsManager.transactionRequestNewFocusCall(mCall, CallState.ANSWERED,
+                new OutcomeReceiver<>() {
+            @Override
+            public void onResult(Boolean result) {
+                Log.d(TAG, "processTransaction: onResult");
+                future.complete(new VoipCallTransactionResult(
+                        VoipCallTransactionResult.RESULT_SUCCEED, null));
+            }
+
+            @Override
+            public void onError(CallException exception) {
+                Log.d(TAG, "processTransaction: onError");
+                future.complete(new VoipCallTransactionResult(
+                        exception.getCode(), exception.getMessage()));
+            }
+        });
+
+        return future;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/voip/CallEventCallbackAckTransaction.java b/src/com/android/server/telecom/voip/CallEventCallbackAckTransaction.java
index 327694e..f47e4c5 100644
--- a/src/com/android/server/telecom/voip/CallEventCallbackAckTransaction.java
+++ b/src/com/android/server/telecom/voip/CallEventCallbackAckTransaction.java
@@ -22,6 +22,7 @@
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.telecom.DisconnectCause;
 import android.util.Log;
 
 import com.android.internal.telecom.ICallEventCallback;
@@ -41,7 +42,10 @@
     private final ICallEventCallback mICallEventCallback;
     private final String mAction;
     private final String mCallId;
-    private final int mVideoState;
+    // optional values
+    private int mVideoState = 0;
+    private DisconnectCause mDisconnectCause = null;
+
     private final VoipCallTransactionResult TRANSACTION_FAILED = new VoipCallTransactionResult(
             CODE_OPERATION_TIMED_OUT, "failed to complete the operation before timeout");
 
@@ -61,6 +65,14 @@
         }
     }
 
+    public CallEventCallbackAckTransaction(ICallEventCallback service, String action,
+            String callId) {
+        mICallEventCallback = service;
+        mAction = action;
+        mCallId = callId;
+    }
+
+
     public CallEventCallbackAckTransaction(ICallEventCallback service, String action, String callId,
             int videoState) {
         mICallEventCallback = service;
@@ -69,6 +81,14 @@
         mVideoState = videoState;
     }
 
+    public CallEventCallbackAckTransaction(ICallEventCallback service, String action, String callId,
+            DisconnectCause cause) {
+        mICallEventCallback = service;
+        mAction = action;
+        mCallId = callId;
+        mDisconnectCause = cause;
+    }
+
 
     @Override
     public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
@@ -82,10 +102,7 @@
                     mICallEventCallback.onSetInactive(mCallId, receiver);
                     break;
                 case TransactionalServiceWrapper.ON_DISCONNECT:
-                    mICallEventCallback.onDisconnect(mCallId, receiver);
-                    break;
-                case TransactionalServiceWrapper.ON_REJECT:
-                    mICallEventCallback.onReject(mCallId, receiver);
+                    mICallEventCallback.onDisconnect(mCallId, mDisconnectCause, receiver);
                     break;
                 case TransactionalServiceWrapper.ON_SET_ACTIVE:
                     mICallEventCallback.onSetActive(mCallId, receiver);
diff --git a/src/com/android/server/telecom/voip/RequestFocusTransaction.java b/src/com/android/server/telecom/voip/RequestFocusTransaction.java
index f13525f..5dedbda 100644
--- a/src/com/android/server/telecom/voip/RequestFocusTransaction.java
+++ b/src/com/android/server/telecom/voip/RequestFocusTransaction.java
@@ -21,6 +21,7 @@
 import android.util.Log;
 
 import com.android.server.telecom.Call;
+import com.android.server.telecom.CallState;
 import com.android.server.telecom.CallsManager;
 
 import java.util.concurrent.CompletableFuture;
@@ -42,7 +43,8 @@
         Log.d(TAG, "processTransaction");
         CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
 
-        mCallsManager.transactionRequestNewFocusCall(mCall, new OutcomeReceiver<>() {
+        mCallsManager.transactionRequestNewFocusCall(mCall, CallState.ACTIVE,
+                new OutcomeReceiver<>() {
             @Override
             public void onResult(Boolean result) {
                 Log.d(TAG, "processTransaction: onResult");
diff --git a/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/MyVoipCall.java b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/MyVoipCall.java
index cdf2efa..690311e 100644
--- a/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/MyVoipCall.java
+++ b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/MyVoipCall.java
@@ -16,11 +16,14 @@
 
 package com.android.server.telecom.transactionalVoipApp;
 
+import android.os.Bundle;
 import android.telecom.CallControlCallback;
 import android.telecom.CallEndpoint;
 import android.telecom.CallControl;
 import android.telecom.CallEventCallback;
+import android.telecom.DisconnectCause;
 import android.util.Log;
+
 import java.util.List;
 
 import androidx.annotation.NonNull;
@@ -60,13 +63,8 @@
     }
 
     @Override
-    public void onReject(@NonNull Consumer<Boolean> wasCompleted) {
-        Log.i(TAG, String.format("onReject: callId=[%s]", mCallId));
-        wasCompleted.accept(Boolean.TRUE);
-    }
-
-    @Override
-    public void onDisconnect(@NonNull Consumer<Boolean> wasCompleted) {
+    public void onDisconnect(@NonNull DisconnectCause cause,
+            @NonNull Consumer<Boolean> wasCompleted) {
         Log.i(TAG, String.format("onDisconnect: callId=[%s]", mCallId));
         wasCompleted.accept(Boolean.TRUE);
     }
@@ -83,6 +81,12 @@
     }
 
     @Override
+    public void onEvent(String event, Bundle extras) {
+        Log.i(TAG, String.format("onEvent: id=[%s], event=[%s], extras=[%s]",
+                mCallId, event, extras));
+    }
+
+    @Override
     public void onCallEndpointChanged(@NonNull CallEndpoint newCallEndpoint) {
         Log.i(TAG, String.format("onCallEndpointChanged: endpoint=[%s]", newCallEndpoint));
     }
diff --git a/tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java b/tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java
index 93d1314..7b1957a 100644
--- a/tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java
@@ -21,11 +21,13 @@
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.ComponentName;
 import android.net.Uri;
+import android.telecom.DisconnectCause;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 
@@ -755,6 +757,90 @@
     }
 
     /**
+     * Emulate the case where a new incoming call is created but the connection fails for a known
+     * reason before being added to CallsManager. In this case, the watchdog should stop tracking
+     * the call and not trigger an anomaly report.
+     */
+    @Test
+    public void testIncomingCallCreatedButNotAddedNoAnomalyReport() {
+        //The call is created:
+        Call call = getCall();
+        call.setState(CallState.NEW, "foo");
+        call.setIsCreateConnectionComplete(false);
+        mCallAnomalyWatchdog.onCallCreated(call);
+
+        //The connection fails before being added to CallsManager for a known reason:
+        call.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED));
+
+        // Move the clock forward:
+        when(mMockClockProxy.elapsedRealtime()).
+                thenReturn(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);
+        mTestScheduledExecutorService.advanceTime(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);
+
+        //Ensure an anomaly report is not generated:
+        verify(mAnomalyReporterAdapter, never()).reportAnomaly(
+                CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_UUID,
+                CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_MSG);
+    }
+
+    /**
+     * Emulate the case where a new outgoing call is created but the connection fails for a known
+     * reason before being added to CallsManager. In this case, the watchdog should stop tracking
+     * the call and not trigger an anomaly report.
+     */
+    @Test
+    public void testOutgoingCallCreatedButNotAddedNoAnomalyReport() {
+        //The call is created:
+        Call call = getCall();
+        call.setCallDirection(Call.CALL_DIRECTION_OUTGOING);
+        call.setState(CallState.NEW, "foo");
+        call.setIsCreateConnectionComplete(false);
+        mCallAnomalyWatchdog.onCallCreated(call);
+
+        //The connection fails before being added to CallsManager for a known reason.
+        call.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED));
+
+        // Move the clock forward:
+        when(mMockClockProxy.elapsedRealtime()).
+                thenReturn(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);
+        mTestScheduledExecutorService.advanceTime(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);
+
+        //Ensure an anomaly report is not generated:
+        verify(mAnomalyReporterAdapter, never()).reportAnomaly(
+                CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_UUID,
+                CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_MSG);
+    }
+
+    /**
+     * Emulate the case where a new incoming call is created but the connection fails for a known
+     * reason before being added to CallsManager and CallsManager notifies the watchdog by invoking
+     * onCallCreatedButNeverAdded(). In this case, the watchdog should stop tracking
+     * the call and not trigger an anomaly report.
+     */
+    @Test
+    public void testCallCreatedButNotAddedPreventsAnomalyReport() {
+        //The call is created:
+        Call call = getCall();
+        call.setState(CallState.NEW, "foo");
+        call.setIsCreateConnectionComplete(false);
+        mCallAnomalyWatchdog.onCallCreated(call);
+
+        //Telecom cancels the connection before adding it to CallsManager:
+        mCallAnomalyWatchdog.onCallCreatedButNeverAdded(call);
+
+        // Move the clock forward:
+        when(mMockClockProxy.elapsedRealtime()).
+                thenReturn(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);
+        mTestScheduledExecutorService.advanceTime(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);
+
+        //Ensure an anomaly report is not generated:
+        verify(mAnomalyReporterAdapter, never()).reportAnomaly(
+                CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_UUID,
+                CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_MSG);
+    }
+
+
+    /**
      * @return an instance of {@link Call} for testing purposes.
      */
     private Call getCall() {
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
index f53c953..569c487 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
@@ -153,7 +153,8 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */);
 
         // Since we don't know if we're on a platform with an earpiece or not, all we can do
         // is ensure the stateMachine construction didn't fail.  But at least we exercised the
@@ -172,7 +173,8 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */);
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         Set<Call> trackedCalls = new HashSet<>(Arrays.asList(fakeCall, fakeSelfManagedCall));
@@ -217,7 +219,8 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */);
         stateMachine.setCallAudioManager(mockCallAudioManager);
         CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER);
@@ -260,7 +263,8 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */);
 
         when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
         when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true);
@@ -305,7 +309,8 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */);
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
@@ -349,7 +354,8 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */);
         stateMachine.setCallAudioManager(mockCallAudioManager);
         Collection<BluetoothDevice> availableDevices = Collections.singleton(bluetoothDevice1);
 
@@ -427,7 +433,8 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */);
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
@@ -463,7 +470,8 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */);
         stateMachine.setCallAudioManager(mockCallAudioManager);
         setInBandRing(false);
         when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
@@ -518,7 +526,8 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */);
         stateMachine.setCallAudioManager(mockCallAudioManager);
         List<BluetoothDevice> availableDevices =
                 Arrays.asList(bluetoothDevice1, bluetoothDevice2, bluetoothDevice3);
@@ -568,7 +577,8 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */);
         stateMachine.setCallAudioManager(mockCallAudioManager);
         when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false);
         CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
@@ -599,7 +609,8 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */);
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false);
@@ -633,7 +644,8 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */);
         stateMachine.setCallAudioManager(mockCallAudioManager);
         List<BluetoothDevice> availableDevices =
                 Arrays.asList(bluetoothDevice1, bluetoothDevice2);
@@ -748,7 +760,8 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */);
         stateMachine.initialize();
         assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
     }
@@ -764,7 +777,8 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */);
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
@@ -801,7 +815,8 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 earpieceControl,
-                mThreadHandler.getLooper());
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */);
         stateMachine.initialize();
         assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
     }
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
index 5caa432..cf684de 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
@@ -269,7 +269,8 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 mParams.earpieceControl,
-                mHandlerThread.getLooper());
+                mHandlerThread.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */);
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         setupMocksForParams(stateMachine, mParams);
@@ -365,7 +366,8 @@
                 mockStatusBarNotifier,
                 mAudioServiceFactory,
                 mParams.earpieceControl,
-                mHandlerThread.getLooper());
+                mHandlerThread.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */);
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         // Set up bluetooth and speakerphone state
diff --git a/tests/src/com/android/server/telecom/tests/CallTest.java b/tests/src/com/android/server/telecom/tests/CallTest.java
index fc78810..6b817d8 100644
--- a/tests/src/com/android/server/telecom/tests/CallTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallTest.java
@@ -26,6 +26,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isA;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -426,11 +427,6 @@
         call.answer(0);
         verify(mMockTransactionalService, times(1)).onAnswer(call, 0);
 
-        // assert CallEventCallback#onReject is called
-        call.setState(CallState.RINGING, "test");
-        call.reject(0);
-        verify(mMockTransactionalService, times(1)).onReject(call, 0);
-
         // assert CallEventCallback#onDisconnect is called
         call.setState(CallState.ACTIVE, "test");
         call.disconnect();
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index 75a9a96..7635ee5 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -48,6 +48,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.media.AudioManager;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
@@ -105,6 +106,7 @@
 import com.android.server.telecom.PhoneNumberUtilsAdapter;
 import com.android.server.telecom.ProximitySensorManager;
 import com.android.server.telecom.ProximitySensorManagerFactory;
+import com.android.server.telecom.Ringer;
 import com.android.server.telecom.RoleManagerAdapter;
 import com.android.server.telecom.SystemStateHelper;
 import com.android.server.telecom.TelecomSystem;
@@ -136,6 +138,7 @@
 import java.util.UUID;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
@@ -233,6 +236,7 @@
     @Mock private Toast mToast;
     @Mock private CallAnomalyWatchdog mCallAnomalyWatchdog;
     @Mock private AnomalyReporterAdapter mAnomalyReporterAdapter;
+    @Mock private Ringer.AccessibilityManagerAdapter mAccessibilityManagerAdapter;
 
     private CallsManager mCallsManager;
 
@@ -252,7 +256,7 @@
         when(mCallEndpointControllerFactory.create(any(), any(), any())).thenReturn(
                 mCallEndpointController);
         when(mCallAudioRouteStateMachineFactory.create(any(), any(), any(), any(), any(), any(),
-                anyInt())).thenReturn(mCallAudioRouteStateMachine);
+                anyInt(), any())).thenReturn(mCallAudioRouteStateMachine);
         when(mCallAudioModeStateMachineFactory.create(any(), any()))
                 .thenReturn(mCallAudioModeStateMachine);
         when(mClockProxy.currentTimeMillis()).thenReturn(System.currentTimeMillis());
@@ -294,7 +298,10 @@
                 mRoleManagerAdapter,
                 mToastFactory,
                 mCallEndpointControllerFactory,
-                mCallAnomalyWatchdog);
+                mCallAnomalyWatchdog,
+                mAccessibilityManagerAdapter,
+                // Just do async tasks synchronously to support testing.
+                command -> command.run());
 
         when(mPhoneAccountRegistrar.getPhoneAccount(
                 eq(SELF_MANAGED_HANDLE), any())).thenReturn(SELF_MANAGED_ACCOUNT);
@@ -1993,6 +2000,79 @@
                 SELF_MANAGED_HANDLE.getUserHandle()));
     }
 
+    /**
+     * Emulate the case where a new incoming call is created but the connection fails for a known
+     * reason before being added to CallsManager. In this case, the listeners should be notified
+     * properly.
+     */
+    @Test
+    public void testIncomingCallCreatedButNotAddedNotifyListener() {
+        //The call is created and a listener is added:
+        Call incomingCall = createCall(SIM_2_HANDLE, null, CallState.NEW);
+        CallsManager.CallsManagerListener listener = mock(CallsManager.CallsManagerListener.class);
+        mCallsManager.addListener(listener);
+
+        //The connection fails before being added to CallsManager for a known reason:
+        incomingCall.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED));
+
+        //Ensure the listener is notified properly:
+        verify(listener).onCallCreatedButNeverAdded(incomingCall);
+    }
+
+    /**
+     * Emulate the case where a new incoming call is created but the connection fails for a known
+     * reason after being added to CallsManager. Since the call was added to CallsManager, the
+     * listeners should not be notified via onCallCreatedButNeverAdded().
+     */
+    @Test
+    public void testIncomingCallCreatedAndAddedDoNotNotifyListener() {
+        //The call is created and a listener is added:
+        Call incomingCall = createCall(SIM_2_HANDLE, null, CallState.NEW);
+        CallsManager.CallsManagerListener listener = mock(CallsManager.CallsManagerListener.class);
+        mCallsManager.addListener(listener);
+
+        //The call is added to CallsManager:
+        mCallsManager.addCall(incomingCall);
+
+        //The connection fails after being added to CallsManager for a known reason:
+        incomingCall.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED));
+
+        //Since the call was added to CallsManager, onCallCreatedButNeverAdded shouldn't be invoked:
+        verify(listener, never()).onCallCreatedButNeverAdded(incomingCall);
+    }
+
+    /**
+     * Emulate the case where a new outgoing call is created but is aborted before being added to
+     * CallsManager since there are no available phone accounts. In this case, the listeners
+     * should be notified properly.
+     */
+    @Test
+    public void testAbortOutgoingCallNoPhoneAccountsNotifyListeners() throws Exception {
+        // Setup a new outgoing call and add a listener
+        Call newCall = addSpyCall(CallState.NEW);
+        CallsManager.CallsManagerListener listener = mock(CallsManager.CallsManagerListener.class);
+        mCallsManager.addListener(listener);
+
+        // Ensure contact info lookup succeeds but do not set the phone account info
+        doAnswer(invocation -> {
+            Uri handle = invocation.getArgument(0);
+            CallerInfo info = new CallerInfo();
+            CompletableFuture<Pair<Uri, CallerInfo>> callerInfoFuture = new CompletableFuture<>();
+            callerInfoFuture.complete(new Pair<>(handle, info));
+            return callerInfoFuture;
+        }).when(mCallerInfoLookupHelper).startLookup(any(Uri.class));
+
+        // Start the outgoing call
+        CompletableFuture<Call> callFuture = mCallsManager.startOutgoingCall(
+                newCall.getHandle(), newCall.getTargetPhoneAccount(), new Bundle(),
+                UserHandle.CURRENT, new Intent(), "com.test.stuff");
+        Call result = callFuture.get(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
+
+        //Ensure the listener is notified properly:
+        verify(listener).onCallCreatedButNeverAdded(any());
+        assertNull(result);
+    }
+
     @Test
     public void testIsInSelfManagedCallOnlySelfManaged() {
         Call selfManagedCall = createCall(SELF_MANAGED_HANDLE, CallState.ACTIVE);
@@ -2415,6 +2495,50 @@
 
     @MediumTest
     @Test
+    public void testSetCallDialingAndDontIncreaseVolume() {
+        // Start with a non zero volume.
+        mComponentContextFixture.getAudioManager().setStreamVolume(AudioManager.STREAM_VOICE_CALL,
+                4, 0 /* flags */);
+
+        Call call = mock(Call.class);
+        mCallsManager.markCallAsDialing(call);
+
+        // We set the volume to non-zero above, so expect 1
+        verify(mComponentContextFixture.getAudioManager(), times(1)).setStreamVolume(
+                eq(AudioManager.STREAM_VOICE_CALL), anyInt(), anyInt());
+    }
+    @MediumTest
+    @Test
+    public void testSetCallDialingAndIncreaseVolume() {
+        // Start with a zero volume stream.
+        mComponentContextFixture.getAudioManager().setStreamVolume(AudioManager.STREAM_VOICE_CALL,
+                0, 0 /* flags */);
+
+        Call call = mock(Call.class);
+        mCallsManager.markCallAsDialing(call);
+
+        // We set the volume to zero above, so expect 2
+        verify(mComponentContextFixture.getAudioManager(), times(2)).setStreamVolume(
+                eq(AudioManager.STREAM_VOICE_CALL), anyInt(), anyInt());
+    }
+
+    @MediumTest
+    @Test
+    public void testSetCallActiveAndDontIncreaseVolume() {
+        // Start with a non-zero volume.
+        mComponentContextFixture.getAudioManager().setStreamVolume(AudioManager.STREAM_VOICE_CALL,
+                4, 0 /* flags */);
+
+        Call call = mock(Call.class);
+        mCallsManager.markCallAsActive(call);
+
+        // We set the volume to non-zero above, so expect 1 only.
+        verify(mComponentContextFixture.getAudioManager(), times(1)).setStreamVolume(
+                eq(AudioManager.STREAM_VOICE_CALL), anyInt(), anyInt());
+    }
+
+    @MediumTest
+    @Test
     public void testHandoverToIsAccepted() {
         Call sourceCall = addSpyCall(CONNECTION_MGR_1_HANDLE, CallState.NEW);
         Call call = addSpyCall(CONNECTION_MGR_1_HANDLE, CallState.NEW);
diff --git a/tests/src/com/android/server/telecom/tests/CarModeTrackerTest.java b/tests/src/com/android/server/telecom/tests/CarModeTrackerTest.java
index 4ad46ae..6056747 100644
--- a/tests/src/com/android/server/telecom/tests/CarModeTrackerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CarModeTrackerTest.java
@@ -236,6 +236,28 @@
     }
 
     /**
+     * Verifies that setting automotive projection overrides entering car mode with the highest
+     * priority of 0. Also ensures exiting car mode doesn't interfere with the automotive
+     * projection being set.
+     */
+    @Test
+    public void testInterleaveCarModeAndProjectionMode() {
+        mCarModeTracker.handleEnterCarMode(0, CAR_MODE_APP1_PACKAGE_NAME);
+        assertEquals(CAR_MODE_APP1_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage());
+        assertTrue(mCarModeTracker.isInCarMode());
+
+        mCarModeTracker.handleSetAutomotiveProjection(CAR_MODE_APP2_PACKAGE_NAME);
+        assertEquals(CAR_MODE_APP2_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage());
+        assertTrue(mCarModeTracker.isInCarMode());
+
+        mCarModeTracker.handleExitCarMode(0, CAR_MODE_APP1_PACKAGE_NAME);
+        assertEquals(CAR_MODE_APP2_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage());
+        assertTrue(mCarModeTracker.isInCarMode());
+
+        mCarModeTracker.handleReleaseAutomotiveProjection();
+    }
+
+    /**
      * Verifies that if we set automotive projection more than once with the same package, nothing
      * changes.
      */
diff --git a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
index 0743805..e3a1471 100644
--- a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
@@ -87,7 +87,7 @@
 import java.util.Map;
 import java.util.concurrent.Executor;
 
-import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
+import static android.content.Context.DEVICE_ID_DEFAULT;
 
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.matches;
@@ -783,6 +783,10 @@
         return mTelephonyManager;
     }
 
+    public AudioManager getAudioManager() {
+        return mAudioManager;
+    }
+
     public CarrierConfigManager getCarrierConfigManager() {
         return mCarrierConfigManager;
     }
diff --git a/tests/src/com/android/server/telecom/tests/HeadsetMediaButtonTest.java b/tests/src/com/android/server/telecom/tests/HeadsetMediaButtonTest.java
index 0bfa987..ce23724 100644
--- a/tests/src/com/android/server/telecom/tests/HeadsetMediaButtonTest.java
+++ b/tests/src/com/android/server/telecom/tests/HeadsetMediaButtonTest.java
@@ -18,6 +18,7 @@
 
 import android.content.Intent;
 import android.media.session.MediaSession;
+import android.telecom.CallEndpoint;
 import android.test.suitebuilder.annotation.MediumTest;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.view.KeyEvent;
@@ -80,7 +81,7 @@
     }
 
     /**
-     * Nominal case; just add a call and remove it.
+     * Nominal case; just add a call and remove it; this happens when the audio state is earpiece.
      */
     @SmallTest
     @Test
@@ -90,14 +91,95 @@
         when(mMockCallsManager.hasAnyCalls()).thenReturn(true);
         mHeadsetMediaButton.onCallAdded(regularCall);
         waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+        verify(mMediaSessionAdapter, never()).setActive(eq(true));
+
+        // Report that the endpoint is earpiece and other routes that don't matter
+        mHeadsetMediaButton.onCallEndpointChanged(
+                new CallEndpoint("Earpiece", CallEndpoint.TYPE_EARPIECE));
+        mHeadsetMediaButton.onCallEndpointChanged(
+                new CallEndpoint("Speaker", CallEndpoint.TYPE_SPEAKER));
+        mHeadsetMediaButton.onCallEndpointChanged(
+                new CallEndpoint("BT", CallEndpoint.TYPE_BLUETOOTH));
+        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+        verify(mMediaSessionAdapter, never()).setActive(eq(true));
+
+        // ... and thus we see how the original code isn't amenable to tests.
+        when(mMediaSessionAdapter.isActive()).thenReturn(false);
+
+        // Still should not have done anything; we never hit wired headset
+        mHeadsetMediaButton.onCallRemoved(regularCall);
+        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+        verify(mMediaSessionAdapter, never()).setActive(eq(false));
+    }
+
+    /**
+     * Call is added and then routed to headset after call start
+     */
+    @SmallTest
+    @Test
+    public void testAddCallThenRouteToHeadset() {
+        Call regularCall = getRegularCall();
+
+        mHeadsetMediaButton.onCallAdded(regularCall);
+        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+        verify(mMediaSessionAdapter, never()).setActive(eq(true));
+
+        mHeadsetMediaButton.onCallEndpointChanged(
+                new CallEndpoint("Wired Headset", CallEndpoint.TYPE_WIRED_HEADSET));
+        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
         verify(mMediaSessionAdapter).setActive(eq(true));
+
         // ... and thus we see how the original code isn't amenable to tests.
         when(mMediaSessionAdapter.isActive()).thenReturn(true);
 
-        when(mMockCallsManager.hasAnyCalls()).thenReturn(false);
+        // Remove the one call; we should release the session.
         mHeadsetMediaButton.onCallRemoved(regularCall);
         waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
         verify(mMediaSessionAdapter).setActive(eq(false));
+        when(mMediaSessionAdapter.isActive()).thenReturn(false);
+
+        // Add a new call; make sure we go active once more.
+        mHeadsetMediaButton.onCallAdded(regularCall);
+        mHeadsetMediaButton.onCallEndpointChanged(
+                new CallEndpoint("Wired Headset", CallEndpoint.TYPE_WIRED_HEADSET));
+        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+        verify(mMediaSessionAdapter, times(2)).setActive(eq(true));
+    }
+
+    /**
+     * Call is added and then routed to headset after call start
+     */
+    @SmallTest
+    @Test
+    public void testAddCallThenRouteToHeadsetAndBack() {
+        Call regularCall = getRegularCall();
+
+        when(mMockCallsManager.hasAnyCalls()).thenReturn(true);
+        mHeadsetMediaButton.onCallAdded(regularCall);
+        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+        verify(mMediaSessionAdapter, never()).setActive(eq(true));
+
+        mHeadsetMediaButton.onCallEndpointChanged(
+                new CallEndpoint("Wired Headset", CallEndpoint.TYPE_WIRED_HEADSET));
+        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+        verify(mMediaSessionAdapter).setActive(eq(true));
+
+        // ... and thus we see how the original code isn't amenable to tests.
+        when(mMediaSessionAdapter.isActive()).thenReturn(true);
+
+        mHeadsetMediaButton.onCallEndpointChanged(
+                new CallEndpoint("Earpiece", CallEndpoint.TYPE_EARPIECE));
+        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+        verify(mMediaSessionAdapter).setActive(eq(false));
+        when(mMediaSessionAdapter.isActive()).thenReturn(false);
+
+        // Remove the one call; we should not release again.
+        mHeadsetMediaButton.onCallRemoved(regularCall);
+        waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+        // Remember, mockito counts total invocations; we should have went active once and then
+        // inactive again when we hit earpiece.
+        verify(mMediaSessionAdapter, times(1)).setActive(eq(true));
+        verify(mMediaSessionAdapter, times(1)).setActive(eq(false));
     }
 
     /**
@@ -111,6 +193,8 @@
         // Start with a regular old call.
         when(mMockCallsManager.hasAnyCalls()).thenReturn(true);
         mHeadsetMediaButton.onCallAdded(regularCall);
+        mHeadsetMediaButton.onCallEndpointChanged(
+                new CallEndpoint("Wired Headset", CallEndpoint.TYPE_WIRED_HEADSET));
         waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
         verify(mMediaSessionAdapter).setActive(eq(true));
         when(mMediaSessionAdapter.isActive()).thenReturn(true);
@@ -122,6 +206,7 @@
         // Expect to set session inactive.
         waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
         verify(mMediaSessionAdapter).setActive(eq(false));
+        when(mMediaSessionAdapter.isActive()).thenReturn(false);
 
         // For good measure lets make it non-external again.
         when(regularCall.isExternalCall()).thenReturn(false);
@@ -129,7 +214,7 @@
         mHeadsetMediaButton.onExternalCallChanged(regularCall, false);
         // Expect to set session active.
         waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
-        verify(mMediaSessionAdapter).setActive(eq(true));
+        verify(mMediaSessionAdapter, times(2)).setActive(eq(true));
     }
 
     @MediumTest
@@ -139,6 +224,8 @@
         when(externalCall.isExternalCall()).thenReturn(true);
 
         mHeadsetMediaButton.onCallAdded(externalCall);
+        mHeadsetMediaButton.onCallEndpointChanged(
+                new CallEndpoint("Wired Headset", CallEndpoint.TYPE_WIRED_HEADSET));
         waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
         verify(mMediaSessionAdapter, never()).setActive(eq(true));
 
diff --git a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
index f50753d..1fd5027 100644
--- a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
+++ b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
@@ -74,6 +74,7 @@
 import android.os.Looper;
 import android.os.Process;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.permission.PermissionCheckerManager;
 import android.telecom.CallAudioState;
 import android.telecom.InCallService;
@@ -150,6 +151,7 @@
     @Mock PermissionInfo mMockPermissionInfo;
     @Mock InCallController.InCallServiceInfo mInCallServiceInfo;
     @Mock private AnomalyReporterAdapter mAnomalyReporterAdapter;
+    @Mock UserManager mMockUserManager;
 
     @Rule
     public TestRule compatChangeRule = new PlatformCompatChangeRule();
@@ -180,6 +182,7 @@
     private static final PhoneAccountHandle PA_HANDLE =
             new PhoneAccountHandle(new ComponentName("pa_pkg", "pa_cls"),
                     "pa_id_0", UserHandle.of(CURRENT_USER_ID));
+    private static final UserHandle DUMMY_USER_HANDLE = UserHandle.of(10);
 
     private UserHandle mUserHandle = UserHandle.of(CURRENT_USER_ID);
     private InCallController mInCallController;
@@ -462,6 +465,9 @@
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
         when(mMockCall.isEmergencyCall()).thenReturn(true);
+        when(mMockContext.getSystemService(eq(UserManager.class)))
+            .thenReturn(mMockUserManager);
+        when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(false);
         when(mMockCall.isIncoming()).thenReturn(false);
         when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
         when(mMockCall.getIntentExtras()).thenReturn(callExtras);
@@ -539,6 +545,9 @@
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockCallsManager.isInEmergencyCall()).thenReturn(true);
         when(mMockCall.isEmergencyCall()).thenReturn(true);
+        when(mMockContext.getSystemService(eq(UserManager.class)))
+            .thenReturn(mMockUserManager);
+        when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(false);
         when(mMockCall.isIncoming()).thenReturn(false);
         when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
         when(mMockCall.getIntentExtras()).thenReturn(callExtras);
@@ -598,6 +607,60 @@
                 eq(Manifest.permission.ACCESS_FINE_LOCATION), eq(mUserHandle));
     }
 
+    @MediumTest
+    @Test
+    public void
+    testBindToService_UserAssociatedWithCallIsInQuietMode_EmergCallInCallUi_BindsToPrimaryUser()
+        throws Exception {
+        when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
+        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+        when(mMockCall.isEmergencyCall()).thenReturn(true);
+        when(mMockCall.getUserHandleFromTargetPhoneAccount()).thenReturn(DUMMY_USER_HANDLE);
+        when(mMockContext.getSystemService(eq(UserManager.class)))
+            .thenReturn(mMockUserManager);
+        when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(true);
+        setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
+        setupMockPackageManagerLocationPermission(SYS_PKG, false /* granted */);
+
+        mInCallController.bindToServices(mMockCall);
+
+        ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mMockContext, times(1)).bindServiceAsUser(
+            bindIntentCaptor.capture(),
+            any(ServiceConnection.class),
+            eq(serviceBindingFlags),
+            eq(mUserHandle));
+        Intent bindIntent = bindIntentCaptor.getValue();
+        assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
+    }
+
+    @MediumTest
+    @Test
+    public void
+    testBindToService_UserAssociatedWithCallNotInQuietMode_EmergCallInCallUi_BindsToAssociatedUser()
+        throws Exception {
+        when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
+        when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+        when(mMockCall.isEmergencyCall()).thenReturn(true);
+        when(mMockCall.getUserHandleFromTargetPhoneAccount()).thenReturn(DUMMY_USER_HANDLE);
+        when(mMockContext.getSystemService(eq(UserManager.class)))
+            .thenReturn(mMockUserManager);
+        when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(false);
+        setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
+        setupMockPackageManagerLocationPermission(SYS_PKG, false /* granted */);
+
+        mInCallController.bindToServices(mMockCall);
+
+        ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+        verify(mMockContext, times(1)).bindServiceAsUser(
+            bindIntentCaptor.capture(),
+            any(ServiceConnection.class),
+            eq(serviceBindingFlags),
+            eq(DUMMY_USER_HANDLE));
+        Intent bindIntent = bindIntentCaptor.getValue();
+        assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
+    }
+
     /**
      * This test verifies the behavior of Telecom when the system dialer crashes on binding and must
      * be restarted.  Specifically, it ensures when the system dialer crashes we revoke the runtime
@@ -614,6 +677,9 @@
         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
         when(mMockCallsManager.isInEmergencyCall()).thenReturn(true);
         when(mMockCall.isEmergencyCall()).thenReturn(true);
+        when(mMockContext.getSystemService(eq(UserManager.class)))
+            .thenReturn(mMockUserManager);
+        when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(false);
         when(mMockCall.isIncoming()).thenReturn(false);
         when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
         when(mMockCall.getIntentExtras()).thenReturn(callExtras);
diff --git a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
index c6cbbbb..cef032f 100644
--- a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
+++ b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
@@ -1667,6 +1667,23 @@
         }
     }
 
+    /**
+     * Ensure an IllegalArgumentException is thrown when providing a SubscriptionAddress that
+     * exceeds the PhoneAccountRegistrar limit.
+     */
+    @Test
+    public void testLimitOnSubscriptionAddress() throws Exception {
+        String text = "a".repeat(100);
+        PhoneAccount.Builder builder =  new PhoneAccount.Builder(makeQuickAccountHandle(TEST_ID),
+                TEST_LABEL).setSubscriptionAddress(Uri.fromParts(text, text, text));
+        try {
+            mRegistrar.enforceCharacterLimit(builder.build());
+            fail("failed to throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // pass test
+        }
+    }
+
     private static PhoneAccount.Builder makeBuilderWithBindCapabilities(PhoneAccountHandle handle) {
         return new PhoneAccount.Builder(handle, TEST_LABEL)
                 .setCapabilities(PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS);
diff --git a/tests/src/com/android/server/telecom/tests/RingerTest.java b/tests/src/com/android/server/telecom/tests/RingerTest.java
index 35eda43..e4050b6 100644
--- a/tests/src/com/android/server/telecom/tests/RingerTest.java
+++ b/tests/src/com/android/server/telecom/tests/RingerTest.java
@@ -20,6 +20,7 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -44,6 +45,7 @@
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
@@ -150,7 +152,7 @@
     @Mock Vibrator mockVibrator;
     @Mock InCallController mockInCallController;
     @Mock NotificationManager mockNotificationManager;
-    @Mock AccessibilityManager mockAccessibilityManager;
+    @Mock Ringer.AccessibilityManagerAdapter mockAccessibilityManagerAdapter;
 
     @Spy Ringer.VibrationEffectProxy spyVibrationEffectProxy;
 
@@ -180,13 +182,12 @@
         when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
         when(mockSystemSettingsUtil.isHapticPlaybackSupported(any(Context.class))).thenReturn(true);
         mockNotificationManager =mContext.getSystemService(NotificationManager.class);
-        mockAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
         when(mockTonePlayer.startTone()).thenReturn(true);
         when(mockNotificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(true);
         when(mockRingtoneFactory.hasHapticChannels(any(Ringtone.class))).thenReturn(false);
         mRingerUnderTest = new Ringer(mockPlayerFactory, mContext, mockSystemSettingsUtil,
                 mockRingtonePlayer, mockRingtoneFactory, mockVibrator, spyVibrationEffectProxy,
-                mockInCallController, mockNotificationManager, mockAccessibilityManager);
+                mockInCallController, mockNotificationManager, mockAccessibilityManagerAdapter);
         when(mockCall1.getState()).thenReturn(CallState.RINGING);
         when(mockCall2.getState()).thenReturn(CallState.RINGING);
         when(mockCall1.getUserHandleFromTargetPhoneAccount()).thenReturn(PA_HANDLE.getUserHandle());
@@ -558,7 +559,7 @@
 
         assertFalse(mRingerUnderTest.shouldRingForContact(mockCall2));
         assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
-        verify(mockAccessibilityManager, never())
+        verify(mockAccessibilityManagerAdapter, never())
                 .startFlashNotificationSequence(any(Context.class), anyInt());
     }
 
@@ -573,7 +574,7 @@
 
         assertTrue(mRingerUnderTest.shouldRingForContact(mockCall2));
         assertTrue(mRingerUnderTest.startRinging(mockCall2, false));
-        verify(mockAccessibilityManager, atLeastOnce())
+        verify(mockAccessibilityManagerAdapter, atLeastOnce())
                 .startFlashNotificationSequence(any(Context.class), anyInt());
     }
 
@@ -589,11 +590,25 @@
         assertTrue(mRingerUnderTest.shouldRingForContact(mockCall2));
         assertTrue(mRingerUnderTest.startRinging(mockCall2, false));
         mRingerUnderTest.stopRinging();
-        verify(mockAccessibilityManager, atLeastOnce())
+        verify(mockAccessibilityManagerAdapter, atLeastOnce())
                 .stopFlashNotificationSequence(any(Context.class));
 
     }
 
+    @SmallTest
+    @Test
+    public void testNoRingingForQuietProfile() {
+        UserManager um = mContext.getSystemService(UserManager.class);
+        when(um.isManagedProfile(PA_HANDLE.getUserHandle().getIdentifier())).thenReturn(true);
+        when(um.isQuietModeEnabled(PA_HANDLE.getUserHandle())).thenReturn(true);
+        // We don't want to acquire audio focus when self-managed
+        assertFalse(mRingerUnderTest.startRinging(mockCall2, true));
+        verify(mockTonePlayer, never()).stopTone();
+        verify(mockRingtonePlayer, never()).play(any(Ringtone.class));
+        verify(mockVibrator, never())
+                .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
+    }
+
     private void ensureRingerIsAudible() {
         when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
         when(mockAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(100);
diff --git a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
index 569d6b4..91728c2 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
@@ -1125,7 +1125,7 @@
             boolean shouldNonEmergencyBeAllowed) {
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mUserCallIntentProcessor).processIntent(intentCaptor.capture(), anyString(),
-                eq(shouldNonEmergencyBeAllowed), eq(true));
+                eq(false), eq(shouldNonEmergencyBeAllowed), eq(true));
         Intent capturedIntent = intentCaptor.getValue();
         assertEquals(Intent.ACTION_CALL, capturedIntent.getAction());
         assertEquals(expectedHandle, capturedIntent.getData());
@@ -1148,7 +1148,7 @@
         }
 
         verify(mUserCallIntentProcessor, never())
-                .processIntent(any(Intent.class), anyString(), anyBoolean(), eq(true));
+                .processIntent(any(Intent.class), anyString(), eq(false), anyBoolean(), eq(true));
     }
 
     @SmallTest
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index 2ab3160..20a7fc8 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -89,6 +89,7 @@
 import com.android.server.telecom.PhoneNumberUtilsAdapterImpl;
 import com.android.server.telecom.ProximitySensorManager;
 import com.android.server.telecom.ProximitySensorManagerFactory;
+import com.android.server.telecom.Ringer;
 import com.android.server.telecom.RoleManagerAdapter;
 import com.android.server.telecom.StatusBarNotifier;
 import com.android.server.telecom.SystemStateHelper;
@@ -112,6 +113,7 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -209,6 +211,8 @@
     @Mock ToneGenerator mToneGenerator;
     @Mock DeviceIdleControllerAdapter mDeviceIdleControllerAdapter;
 
+    @Mock Ringer.AccessibilityManagerAdapter mAccessibilityManagerAdapter;
+
     final ComponentName mInCallServiceComponentNameX =
             new ComponentName(
                     "incall-service-package-X",
@@ -509,7 +513,8 @@
                             WiredHeadsetManager wiredHeadsetManager,
                             StatusBarNotifier statusBarNotifier,
                             CallAudioManager.AudioServiceFactory audioServiceFactory,
-                            int earpieceControl) {
+                            int earpieceControl,
+                            Executor asyncTaskExecutor) {
                         return new CallAudioRouteStateMachine(context,
                                 callsManager,
                                 bluetoothManager,
@@ -518,7 +523,8 @@
                                 audioServiceFactory,
                                 // Force enable an earpiece for the end-to-end tests
                                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
-                                mHandlerThread.getLooper());
+                                mHandlerThread.getLooper(),
+                                Runnable::run /* async tasks as now sync for testing! */);
                     }
                 },
                 new CallAudioModeStateMachine.Factory() {
@@ -537,7 +543,8 @@
                             ContactsAsyncHelper.ContentResolverAdapter adapter) {
                         return new ContactsAsyncHelper(adapter, mHandlerThread.getLooper());
                     }
-                }, mDeviceIdleControllerAdapter);
+                }, mDeviceIdleControllerAdapter, mAccessibilityManagerAdapter,
+                Runnable::run);
 
         mComponentContextFixture.setTelecomManager(new TelecomManager(
                 mComponentContextFixture.getTestDouble(),
@@ -754,7 +761,7 @@
         final UserHandle userHandle = initiatingUser;
         Context localAppContext = mComponentContextFixture.getTestDouble().getApplicationContext();
         new UserCallIntentProcessor(localAppContext, userHandle).processIntent(
-                actionCallIntent, null, true /* hasCallAppOp*/, false /* isLocal */);
+                actionCallIntent, null, false, true /* hasCallAppOp*/, false /* isLocal */);
         // Wait for handler to start CallerInfo lookup.
         waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
         // Send the CallerInfo lookup reply.
diff --git a/tests/src/com/android/server/telecom/tests/TransactionTests.java b/tests/src/com/android/server/telecom/tests/TransactionTests.java
index a82fa78..1e6734b 100644
--- a/tests/src/com/android/server/telecom/tests/TransactionTests.java
+++ b/tests/src/com/android/server/telecom/tests/TransactionTests.java
@@ -47,6 +47,7 @@
 import com.android.server.telecom.PhoneNumberUtilsAdapter;
 import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.ui.ToastFactory;
+import com.android.server.telecom.voip.AnswerCallTransaction;
 import com.android.server.telecom.voip.EndCallTransaction;
 import com.android.server.telecom.voip.HoldCallTransaction;
 import com.android.server.telecom.voip.IncomingCallTransaction;
@@ -152,7 +153,23 @@
 
         // THEN
         verify(mCallsManager, times(1))
-                .transactionRequestNewFocusCall(eq(mMockCall1), isA(OutcomeReceiver.class));
+                .transactionRequestNewFocusCall(eq(mMockCall1), eq(CallState.ACTIVE),
+                        isA(OutcomeReceiver.class));
+    }
+
+    @Test
+    public void testAnswerCallTransaction() throws Exception {
+        // GIVEN
+        AnswerCallTransaction transaction =
+                new AnswerCallTransaction(mCallsManager, mMockCall1, 0);
+
+        // WHEN
+        transaction.processTransaction(null);
+
+        // THEN
+        verify(mCallsManager, times(1))
+                .transactionRequestNewFocusCall(eq(mMockCall1), eq(CallState.ANSWERED),
+                        isA(OutcomeReceiver.class));
     }
 
     @Test