Adjust avatar used for call streaming notification. am: 8a0898a7b6 am: fb2b1b085f

Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/services/Telecomm/+/23181934

Change-Id: I2d6acba9b8d07a69687342b23b3a37059f59a6ee
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/res/values-gu/strings.xml b/res/values-gu/strings.xml
index 883ce52..4af6351 100644
--- a/res/values-gu/strings.xml
+++ b/res/values-gu/strings.xml
@@ -25,7 +25,7 @@
     <string name="notification_missedCallsMsg" msgid="5055782736170916682">"<xliff:g id="NUM_MISSED_CALLS">%s</xliff:g> ચૂકી ગયેલા કૉલ"</string>
     <string name="notification_missedCallTicker" msgid="6731461957487087769">"<xliff:g id="MISSED_CALL_FROM">%s</xliff:g> નો કૉલ ચૂકી ગયાં"</string>
     <string name="notification_missedCall_call_back" msgid="7900333283939789732">"કૉલ બેક"</string>
-    <string name="notification_missedCall_message" msgid="4054698824390076431">"મેસેજ"</string>
+    <string name="notification_missedCall_message" msgid="4054698824390076431">"સંદેશ"</string>
     <string name="notification_disconnectedCall_title" msgid="1790131923692416928">"ડિસ્કનેક્ટ કરેલો કૉલ"</string>
     <string name="notification_disconnectedCall_body" msgid="600491714584417536">"ઇમર્જન્સી કૉલને કારણે <xliff:g id="CALLER">%s</xliff:g>નો કૉલ ડિસ્કનેક્ટ કરવામાં આવ્યો છે."</string>
     <string name="notification_disconnectedCall_generic_body" msgid="5282765206349184853">"ઇમર્જન્સી કૉલને કારણે તમારો કૉલ ડિસ્કનેક્ટ કરવામાં આવ્યો છે."</string>
diff --git a/res/values-kn/strings.xml b/res/values-kn/strings.xml
index 68d8078..8109de2 100644
--- a/res/values-kn/strings.xml
+++ b/res/values-kn/strings.xml
@@ -53,7 +53,7 @@
     <string name="no_vm_number" msgid="2179959110602180844">"ಧ್ವನಿಮೇಲ್‌ ಸಂಖ್ಯೆಯು ಕಾಣೆಯಾಗಿದೆ"</string>
     <string name="no_vm_number_msg" msgid="1339245731058529388">"ಸಿಮ್‌ ಕಾರ್ಡ್‌ನಲ್ಲಿ ಯಾವುದೇ ಧ್ವನಿಮೇಲ್‌ ಸಂಖ್ಯೆಯನ್ನು ಸಂಗ್ರಹಿಸಿಲ್ಲ."</string>
     <string name="add_vm_number_str" msgid="5179510133063168998">"ಸಂಖ್ಯೆಯನ್ನು ಸೇರಿಸಿ"</string>
-    <string name="change_default_dialer_dialog_title" msgid="5861469279421508060">"<xliff:g id="NEW_APP">%s</xliff:g> ಅನ್ನು ನಿಮ್ಮ ಡಿಫಾಲ್ಟ್ ಫೋನ್ ಆ್ಯಪ್ ಆಗಿ ಮಾಡಬೇಕೆ?"</string>
+    <string name="change_default_dialer_dialog_title" msgid="5861469279421508060">"<xliff:g id="NEW_APP">%s</xliff:g> ಅನ್ನು ನಿಮ್ಮ ಡಿಫಾಲ್ಟ್ ಫೋನ್ ಅಪ್ಲಿಕೇಶನ್ ಆಗಿ ಮಾಡುವುದೇ?"</string>
     <string name="change_default_dialer_dialog_affirmative" msgid="8604665314757739550">"ಡಿಫಾಲ್ಟ್ ಹೊಂದಿಸಿ"</string>
     <string name="change_default_dialer_dialog_negative" msgid="8648669840052697821">"ರದ್ದುಮಾಡಿ"</string>
     <string name="change_default_dialer_warning_message" msgid="8461963987376916114">"<xliff:g id="NEW_APP">%s</xliff:g> ಗೆ ನಿಮ್ಮ ಕರೆಗಳ ಎಲ್ಲಾ ಅಂಶಗಳನ್ನು ನಿಯಂತ್ರಿಸಲು ಮತ್ತು ಕರೆಗಳನ್ನು ಮಾಡಲು ಸಾಧ್ಯವಾಗುತ್ತದೆ. ನೀವು ವಿಶ್ವಾಸವಿರಿಸಿರುವಂತಹ ಅಪ್ಲಿಕೇಶನ್‌ಗಳನ್ನು ಮಾತ್ರ ನಿಮ್ಮ ಡಿಫಾಲ್ಟ್ ಅಪ್ಲಿಕೇಶನ್ ಆಗಿ ಹೊಂದಿಸಬೇಕು."</string>
diff --git a/src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java b/src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java
new file mode 100644
index 0000000..43624a3
--- /dev/null
+++ b/src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom;
+
+import android.bluetooth.BluetoothDevice;
+import android.content.Context;
+import android.media.AudioDeviceInfo;
+import android.media.AudioManager;
+import android.telecom.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.telecom.bluetooth.BluetoothRouteManager;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Helper class used to keep track of the requested communication device within Telecom for audio
+ * use cases. Handles the set/clear communication use case logic for all audio routes (speaker, BT,
+ * headset, and earpiece). For BT devices, this handles switches between hearing aids, SCO, and LE
+ * audio (also takes into account switching between multiple LE audio devices).
+ */
+public class CallAudioCommunicationDeviceTracker {
+
+    // Use -1 indicates device is not set for any communication use case
+    private static final int sAUDIO_DEVICE_TYPE_INVALID = -1;
+    // Possible bluetooth audio device types
+    private static final Set<Integer> sBT_AUDIO_DEVICE_TYPES = Set.of(
+            AudioDeviceInfo.TYPE_BLE_HEADSET,
+            AudioDeviceInfo.TYPE_HEARING_AID,
+            AudioDeviceInfo.TYPE_BLUETOOTH_SCO
+    );
+    private AudioManager mAudioManager;
+    private BluetoothRouteManager mBluetoothRouteManager;
+    private int mAudioDeviceType = sAUDIO_DEVICE_TYPE_INVALID;
+    // Keep track of the locally requested BT audio device if set
+    private String mBtAudioDevice = null;
+
+    public CallAudioCommunicationDeviceTracker(Context context) {
+        mAudioManager = context.getSystemService(AudioManager.class);
+    }
+
+    public void setBluetoothRouteManager(BluetoothRouteManager bluetoothRouteManager) {
+        mBluetoothRouteManager = bluetoothRouteManager;
+    }
+
+    public boolean isAudioDeviceSetForType(int audioDeviceType) {
+        return mAudioDeviceType == audioDeviceType;
+    }
+
+    @VisibleForTesting
+    public void setTestCommunicationDevice(int audioDeviceType) {
+        mAudioDeviceType = audioDeviceType;
+    }
+
+    public void clearBtCommunicationDevice() {
+        if (mBtAudioDevice == null) {
+            Log.i(this, "No bluetooth device was set for communication that can be cleared.");
+            return;
+        }
+        // If mBtAudioDevice is set, we know a BT audio device was set for communication so
+        // mAudioDeviceType corresponds to a BT device type (e.g. hearing aid, SCO, LE).
+        clearCommunicationDevice(mAudioDeviceType);
+    }
+
+    /*
+     * Sets the communication device for the passed in audio device type, if it's available for
+     * communication use cases. Tries to clear any communication device which was previously
+     * requested for communication before setting the new device.
+     * @param audioDeviceTypes The supported audio device types for the device.
+     * @param btDevice The bluetooth device to connect to (only used for switching between multiple
+     *        LE audio devices).
+     * @return {@code true} if the device was set for communication, {@code false} if the device
+     * wasn't set.
+     */
+    public boolean setCommunicationDevice(int audioDeviceType,
+            BluetoothDevice btDevice) {
+        // There is only one audio device type associated with each type of BT device.
+        boolean isBtDevice = sBT_AUDIO_DEVICE_TYPES.contains(audioDeviceType);
+        Log.i(this, "setCommunicationDevice: type = %s, isBtDevice = %s, btDevice = %s",
+                audioDeviceType, isBtDevice, btDevice);
+
+        // Account for switching between multiple LE audio devices.
+        boolean handleLeAudioDeviceSwitch = btDevice != null
+                && !btDevice.getAddress().equals(mBtAudioDevice);
+        if ((audioDeviceType == mAudioDeviceType
+                || isUsbHeadsetType(audioDeviceType, mAudioDeviceType))
+                && !handleLeAudioDeviceSwitch) {
+            Log.i(this, "Communication device is already set for this audio type");
+            return false;
+        }
+
+        AudioDeviceInfo activeDevice = null;
+        List<AudioDeviceInfo> devices = mAudioManager.getAvailableCommunicationDevices();
+        if (devices.size() == 0) {
+            Log.w(this, "No communication devices available");
+            return false;
+        }
+
+        for (AudioDeviceInfo device : devices) {
+            Log.i(this, "Available device type: " + device.getType());
+            // Ensure that we do not select the same BT LE audio device for communication.
+            if ((audioDeviceType == device.getType()
+                    || isUsbHeadsetType(audioDeviceType, device.getType()))
+                    && !device.getAddress().equals(mBtAudioDevice)) {
+                activeDevice = device;
+                break;
+            }
+        }
+
+        if (activeDevice == null) {
+            Log.i(this, "No active device of type(s) %s available",
+                    audioDeviceType == AudioDeviceInfo.TYPE_WIRED_HEADSET
+                            ? Arrays.asList(AudioDeviceInfo.TYPE_WIRED_HEADSET,
+                                    AudioDeviceInfo.TYPE_USB_HEADSET)
+                            : audioDeviceType);
+            return false;
+        }
+
+        // Force clear previous communication device, if one was set, before setting the new device.
+        if (mAudioDeviceType != sAUDIO_DEVICE_TYPE_INVALID) {
+            clearCommunicationDevice(mAudioDeviceType);
+        }
+
+        // Turn activeDevice ON.
+        boolean result = mAudioManager.setCommunicationDevice(activeDevice);
+        if (!result) {
+            Log.w(this, "Could not set active device");
+        } else {
+            Log.i(this, "Active device set");
+            mAudioDeviceType = activeDevice.getType();
+            if (isBtDevice) {
+                mBtAudioDevice = activeDevice.getAddress();
+                if (audioDeviceType == AudioDeviceInfo.TYPE_BLE_HEADSET) {
+                    mBluetoothRouteManager.onAudioOn(mBtAudioDevice);
+                }
+            }
+        }
+        return result;
+    }
+
+    /*
+     * Clears the communication device for the passed in audio device types, given that the device
+     * has previously been set for communication.
+     * @param audioDeviceTypes The supported audio device types for the device.
+     */
+    public void clearCommunicationDevice(int audioDeviceType) {
+        // There is only one audio device type associated with each type of BT device.
+        boolean isBtDevice = sBT_AUDIO_DEVICE_TYPES.contains(audioDeviceType);
+        Log.i(this, "clearCommunicationDevice: type = %s, isBtDevice = %s",
+                audioDeviceType, isBtDevice);
+
+        if (audioDeviceType != mAudioDeviceType
+                && !isUsbHeadsetType(audioDeviceType, mAudioDeviceType)) {
+            Log.i(this, "Unable to clear communication device of type(s), %s. "
+                    + "Device does not correspond to the locally requested device type.",
+                    audioDeviceType == AudioDeviceInfo.TYPE_WIRED_HEADSET
+                            ? Arrays.asList(AudioDeviceInfo.TYPE_WIRED_HEADSET,
+                                    AudioDeviceInfo.TYPE_USB_HEADSET)
+                            : audioDeviceType
+            );
+            return;
+        }
+
+        if (isBtDevice && mBtAudioDevice != null) {
+            // Signal that BT audio was lost for device.
+            mBluetoothRouteManager.onAudioLost(mBtAudioDevice);
+            mBtAudioDevice = null;
+        }
+
+        if (mAudioManager == null) {
+            Log.i(this, "clearCommunicationDevice: mAudioManager is null");
+            return;
+        }
+
+        // Clear device and reset locally saved device type.
+        mAudioManager.clearCommunicationDevice();
+        mAudioDeviceType = sAUDIO_DEVICE_TYPE_INVALID;
+    }
+
+    private boolean isUsbHeadsetType(int audioDeviceType, int sourceType) {
+        return audioDeviceType != AudioDeviceInfo.TYPE_WIRED_HEADSET
+                ? false : sourceType == AudioDeviceInfo.TYPE_USB_HEADSET;
+    }
+}
diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
index 531f42e..730ecd5 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -83,7 +83,8 @@
                 StatusBarNotifier statusBarNotifier,
                 CallAudioManager.AudioServiceFactory audioServiceFactory,
                 int earpieceControl,
-                Executor asyncTaskExecutor) {
+                Executor asyncTaskExecutor,
+                CallAudioCommunicationDeviceTracker communicationDeviceTracker) {
             return new CallAudioRouteStateMachine(context,
                     callsManager,
                     bluetoothManager,
@@ -91,7 +92,8 @@
                     statusBarNotifier,
                     audioServiceFactory,
                     earpieceControl,
-                    asyncTaskExecutor);
+                    asyncTaskExecutor,
+                    communicationDeviceTracker);
         }
     }
     /** Values for CallAudioRouteStateMachine constructor's earPieceRouting arg. */
@@ -371,6 +373,8 @@
         public void enter() {
             super.enter();
             setSpeakerphoneOn(false);
+            mCommunicationDeviceTracker.setCommunicationDevice(
+                    AudioDeviceInfo.TYPE_BUILTIN_EARPIECE, null);
             CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_EARPIECE,
                     mAvailableRoutes, null,
                     mBluetoothRouteManager.getConnectedDevices());
@@ -401,6 +405,8 @@
                 case SWITCH_BLUETOOTH:
                 case USER_SWITCH_BLUETOOTH:
                     if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
+                        mCommunicationDeviceTracker.clearCommunicationDevice(
+                                AudioDeviceInfo.TYPE_BUILTIN_EARPIECE);
                         if (mAudioFocusType == ACTIVE_FOCUS
                                 || mBluetoothRouteManager.isInbandRingingEnabled()) {
                             String address = (msg.obj instanceof SomeArgs) ?
@@ -417,6 +423,8 @@
                 case SWITCH_HEADSET:
                 case USER_SWITCH_HEADSET:
                     if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
+                        mCommunicationDeviceTracker.clearCommunicationDevice(
+                                AudioDeviceInfo.TYPE_BUILTIN_EARPIECE);
                         transitionTo(mActiveHeadsetRoute);
                     } else {
                         Log.w(this, "Ignoring switch to headset command. Not available.");
@@ -426,6 +434,8 @@
                     // fall through; we want to switch to speaker mode when docked and in a call.
                 case SWITCH_SPEAKER:
                 case USER_SWITCH_SPEAKER:
+                    mCommunicationDeviceTracker.clearCommunicationDevice(
+                            AudioDeviceInfo.TYPE_BUILTIN_EARPIECE);
                     setSpeakerphoneOn(true);
                     // fall through
                 case SPEAKER_ON:
@@ -579,6 +589,8 @@
         public void enter() {
             super.enter();
             setSpeakerphoneOn(false);
+            mCommunicationDeviceTracker.setCommunicationDevice(
+                    AudioDeviceInfo.TYPE_WIRED_HEADSET, null);
             CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_WIRED_HEADSET,
                     mAvailableRoutes, null, mBluetoothRouteManager.getConnectedDevices());
             setSystemAudioState(newState, true);
@@ -600,6 +612,8 @@
                 case SWITCH_EARPIECE:
                 case USER_SWITCH_EARPIECE:
                     if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
+                        mCommunicationDeviceTracker.clearCommunicationDevice(
+                                AudioDeviceInfo.TYPE_WIRED_HEADSET);
                         transitionTo(mActiveEarpieceRoute);
                     } else {
                         Log.w(this, "Ignoring switch to earpiece command. Not available.");
@@ -615,6 +629,8 @@
                                 || mBluetoothRouteManager.isInbandRingingEnabled()) {
                             String address = (msg.obj instanceof SomeArgs) ?
                                     (String) ((SomeArgs) msg.obj).arg2 : null;
+                            mCommunicationDeviceTracker.clearCommunicationDevice(
+                                    AudioDeviceInfo.TYPE_WIRED_HEADSET);
                             // Omit transition to ActiveBluetoothRoute until actual connection.
                             setBluetoothOn(address);
                         } else {
@@ -631,6 +647,8 @@
                     return HANDLED;
                 case SWITCH_SPEAKER:
                 case USER_SWITCH_SPEAKER:
+                    mCommunicationDeviceTracker.clearCommunicationDevice(
+                            AudioDeviceInfo.TYPE_WIRED_HEADSET);
                     setSpeakerphoneOn(true);
                     // fall through
                 case SPEAKER_ON:
@@ -793,6 +811,12 @@
         public void enter() {
             super.enter();
             setSpeakerphoneOn(false);
+            // Try arbitrarily connecting to BT audio if we haven't already. This handles
+            // the edge case of when the audio route is in a quiescent route while in-call and
+            // the BT connection fails to be set. Previously, the logic was to setBluetoothOn in
+            // ACTIVE_FOCUS but the route would still remain in a quiescent route, so instead we
+            // should be transitioning directly into the active route.
+            setBluetoothOn(null);
             CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_BLUETOOTH,
                     mAvailableRoutes, mBluetoothRouteManager.getBluetoothAudioConnectedDevice(),
                     mBluetoothRouteManager.getConnectedDevices());
@@ -1065,7 +1089,9 @@
                     return HANDLED;
                 case SWITCH_FOCUS:
                     if (msg.arg1 == ACTIVE_FOCUS) {
-                        setBluetoothOn(null);
+                        // It is possible that the connection to BT will fail while in-call, in
+                        // which case, we want to transition into the active route.
+                        transitionTo(mActiveBluetoothRoute);
                     } else if (msg.arg1 == RINGING_FOCUS) {
                         if (mBluetoothRouteManager.isInbandRingingEnabled()) {
                             setBluetoothOn(null);
@@ -1514,6 +1540,7 @@
     private CallAudioState mLastKnownCallAudioState;
 
     private CallAudioManager mCallAudioManager;
+    private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
 
     public CallAudioRouteStateMachine(
             Context context,
@@ -1523,7 +1550,8 @@
             StatusBarNotifier statusBarNotifier,
             CallAudioManager.AudioServiceFactory audioServiceFactory,
             int earpieceControl,
-            Executor asyncTaskExecutor) {
+            Executor asyncTaskExecutor,
+            CallAudioCommunicationDeviceTracker communicationDeviceTracker) {
         super(NAME);
         mContext = context;
         mCallsManager = callsManager;
@@ -1534,6 +1562,7 @@
         mAudioServiceFactory = audioServiceFactory;
         mLock = callsManager.getLock();
         mAsyncTaskExecutor = asyncTaskExecutor;
+        mCommunicationDeviceTracker = communicationDeviceTracker;
         createStates(earpieceControl);
     }
 
@@ -1545,7 +1574,8 @@
             WiredHeadsetManager wiredHeadsetManager,
             StatusBarNotifier statusBarNotifier,
             CallAudioManager.AudioServiceFactory audioServiceFactory,
-            int earpieceControl, Looper looper, Executor asyncTaskExecutor) {
+            int earpieceControl, Looper looper, Executor asyncTaskExecutor,
+            CallAudioCommunicationDeviceTracker communicationDeviceTracker) {
         super(NAME, looper);
         mContext = context;
         mCallsManager = callsManager;
@@ -1556,6 +1586,7 @@
         mAudioServiceFactory = audioServiceFactory;
         mLock = callsManager.getLock();
         mAsyncTaskExecutor = asyncTaskExecutor;
+        mCommunicationDeviceTracker = communicationDeviceTracker;
 
         createStates(earpieceControl);
     }
@@ -1735,25 +1766,13 @@
         // 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;
-                }
+            if (on) {
+                speakerOn = mCommunicationDeviceTracker.setCommunicationDevice(
+                        AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, null);
             } else {
-                AudioDeviceInfo curDevice = mAudioManager.getCommunicationDevice();
-                if (curDevice != null
-                        && curDevice.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
-                    mAudioManager.clearCommunicationDevice();
-                }
+                mCommunicationDeviceTracker.clearCommunicationDevice(
+                        AudioDeviceInfo.TYPE_BUILTIN_SPEAKER);
             }
             mStatusBarNotifier.notifySpeakerphone(hasAnyCalls && speakerOn);
         });
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 13a965c..0a82c4f 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -138,8 +138,8 @@
 import com.android.server.telecom.ui.DisconnectedCallNotifier;
 import com.android.server.telecom.ui.IncomingCallNotifier;
 import com.android.server.telecom.ui.ToastFactory;
-import com.android.server.telecom.voip.TransactionManager;
 import com.android.server.telecom.voip.VoipCallMonitor;
+import com.android.server.telecom.voip.TransactionManager;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -563,6 +563,7 @@
             BlockedNumbersAdapter blockedNumbersAdapter,
             TransactionManager transactionManager,
             EmergencyCallDiagnosticLogger emergencyCallDiagnosticLogger,
+            CallAudioCommunicationDeviceTracker communicationDeviceTracker,
             CallStreamingNotification callStreamingNotification) {
 
         mContext = context;
@@ -594,7 +595,8 @@
                         statusBarNotifier,
                         audioServiceFactory,
                         CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT,
-                        asyncTaskExecutor
+                        asyncTaskExecutor,
+                        communicationDeviceTracker
                 );
         callAudioRouteStateMachine.initialize();
 
@@ -648,10 +650,10 @@
         mClockProxy = clockProxy;
         mToastFactory = toastFactory;
         mRoleManagerAdapter = roleManagerAdapter;
+        mVoipCallMonitor = new VoipCallMonitor(mContext, mLock);
         mTransactionManager = transactionManager;
         mBlockedNumbersAdapter = blockedNumbersAdapter;
         mCallStreamingController = new CallStreamingController(mContext, mLock);
-        mVoipCallMonitor = new VoipCallMonitor(mContext, mLock);
         mCallStreamingNotification = callStreamingNotification;
 
         mListeners.add(mInCallWakeLockController);
@@ -2021,7 +2023,7 @@
                                         + " available accounts.");
                                 showErrorMessage(R.string.cant_call_due_to_no_supported_service);
                                 mListeners.forEach(l -> l.onCreateConnectionFailed(callToPlace));
-                                if (callToPlace.isEmergencyCall()){
+                                if (callToPlace.isEmergencyCall()) {
                                     mAnomalyReporter.reportAnomaly(
                                             EMERGENCY_CALL_ABORTED_NO_PHONE_ACCOUNTS_ERROR_UUID,
                                             EMERGENCY_CALL_ABORTED_NO_PHONE_ACCOUNTS_ERROR_MSG);
diff --git a/src/com/android/server/telecom/RingbackPlayer.java b/src/com/android/server/telecom/RingbackPlayer.java
index a8af3ac..e0c6136 100644
--- a/src/com/android/server/telecom/RingbackPlayer.java
+++ b/src/com/android/server/telecom/RingbackPlayer.java
@@ -19,6 +19,7 @@
 import static com.android.server.telecom.LogUtils.Events.START_RINBACK;
 import static com.android.server.telecom.LogUtils.Events.STOP_RINGBACK;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
 import android.telecom.Log;
 
@@ -42,8 +43,12 @@
      */
     private InCallTonePlayer mTonePlayer;
 
-    RingbackPlayer(InCallTonePlayer.Factory playerFactory) {
+    private final Object mLock;
+
+    @VisibleForTesting
+    public RingbackPlayer(InCallTonePlayer.Factory playerFactory) {
         mPlayerFactory = playerFactory;
+        mLock = new Object();
     }
 
     /**
@@ -52,25 +57,27 @@
      * @param call The call for which to ringback.
      */
     public void startRingbackForCall(Call call) {
-        Preconditions.checkState(call.getState() == CallState.DIALING);
+        synchronized (mLock) {
+            Preconditions.checkState(call.getState() == CallState.DIALING);
 
-        if (mCall == call) {
-            Log.w(this, "Ignoring duplicate requests to ring for %s.", call);
-            return;
-        }
+            if (mCall == call) {
+                Log.w(this, "Ignoring duplicate requests to ring for %s.", call);
+                return;
+            }
 
-        if (mCall != null) {
-            // We only get here for the foreground call so, there's no reason why there should
-            // exist a current dialing call.
-            Log.wtf(this, "Ringback player thinks there are two foreground-dialing calls.");
-        }
+            if (mCall != null) {
+                // We only get here for the foreground call so, there's no reason why there should
+                // exist a current dialing call.
+                Log.wtf(this, "Ringback player thinks there are two foreground-dialing calls.");
+            }
 
-        mCall = call;
-        if (mTonePlayer == null) {
-            Log.i(this, "Playing the ringback tone for %s.", call);
-            Log.addEvent(call, START_RINBACK);
-            mTonePlayer = mPlayerFactory.createPlayer(InCallTonePlayer.TONE_RING_BACK);
-            mTonePlayer.startTone();
+            mCall = call;
+            if (mTonePlayer == null) {
+                Log.i(this, "Playing the ringback tone for %s.", call);
+                Log.addEvent(call, START_RINBACK);
+                mTonePlayer = mPlayerFactory.createPlayer(InCallTonePlayer.TONE_RING_BACK);
+                mTonePlayer.startTone();
+            }
         }
     }
 
@@ -80,19 +87,27 @@
      * @param call The call for which to stop ringback.
      */
     public void stopRingbackForCall(Call call) {
-        if (mCall == call) {
-            // The foreground call is no longer dialing or is no longer the foreground call. In
-            // either case, stop the ringback tone.
-            mCall = null;
+        synchronized (mLock) {
+            if (mCall == call) {
+                // The foreground call is no longer dialing or is no longer the foreground call. In
+                // either case, stop the ringback tone.
+                mCall = null;
 
-            if (mTonePlayer == null) {
-                Log.w(this, "No player found to stop.");
-            } else {
-                Log.i(this, "Stopping the ringback tone for %s.", call);
-                Log.addEvent(call, STOP_RINGBACK);
-                mTonePlayer.stopTone();
-                mTonePlayer = null;
+                if (mTonePlayer == null) {
+                    Log.w(this, "No player found to stop.");
+                } else {
+                    Log.i(this, "Stopping the ringback tone for %s.", call);
+                    Log.addEvent(call, STOP_RINGBACK);
+                    mTonePlayer.stopTone();
+                    mTonePlayer = null;
+                }
             }
         }
     }
+
+    public boolean isRingbackPlaying() {
+        synchronized (mLock) {
+            return mTonePlayer != null;
+        }
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index d3ca0b7..3bfb933 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -244,13 +244,17 @@
                             return context.getContentResolver().openInputStream(uri);
                         }
                     });
+            CallAudioCommunicationDeviceTracker communicationDeviceTracker = new
+                    CallAudioCommunicationDeviceTracker(mContext);
             BluetoothDeviceManager bluetoothDeviceManager = new BluetoothDeviceManager(mContext,
-                    mContext.getSystemService(BluetoothManager.class).getAdapter());
+                    mContext.getSystemService(BluetoothManager.class).getAdapter(),
+                    communicationDeviceTracker);
             BluetoothRouteManager bluetoothRouteManager = new BluetoothRouteManager(mContext, mLock,
-                    bluetoothDeviceManager, new Timeouts.Adapter());
+                    bluetoothDeviceManager, new Timeouts.Adapter(), communicationDeviceTracker);
             BluetoothStateReceiver bluetoothStateReceiver = new BluetoothStateReceiver(
-                    bluetoothDeviceManager, bluetoothRouteManager);
+                    bluetoothDeviceManager, bluetoothRouteManager, communicationDeviceTracker);
             mContext.registerReceiver(bluetoothStateReceiver, BluetoothStateReceiver.INTENT_FILTER);
+            communicationDeviceTracker.setBluetoothRouteManager(bluetoothRouteManager);
 
             WiredHeadsetManager wiredHeadsetManager = new WiredHeadsetManager(mContext);
             SystemStateHelper systemStateHelper = new SystemStateHelper(mContext, mLock);
@@ -394,6 +398,7 @@
                     blockedNumbersAdapter,
                     transactionManager,
                     emergencyCallDiagnosticLogger,
+                    communicationDeviceTracker,
                     callStreamingNotification);
 
             mIncomingCallNotifier = incomingCallNotifier;
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
index 473e7b9..4482ec6 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
@@ -32,6 +32,7 @@
 import android.util.LocalLog;
 
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -194,8 +195,10 @@
     private BluetoothAdapter mBluetoothAdapter;
     private AudioManager mAudioManager;
     private Executor mExecutor;
+    private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
 
-    public BluetoothDeviceManager(Context context, BluetoothAdapter bluetoothAdapter) {
+    public BluetoothDeviceManager(Context context, BluetoothAdapter bluetoothAdapter,
+            CallAudioCommunicationDeviceTracker communicationDeviceTracker) {
         if (bluetoothAdapter != null) {
             mBluetoothAdapter = bluetoothAdapter;
             bluetoothAdapter.getProfileProxy(context, mBluetoothProfileServiceListener,
@@ -206,6 +209,7 @@
                     BluetoothProfile.LE_AUDIO);
             mAudioManager = context.getSystemService(AudioManager.class);
             mExecutor = context.getMainExecutor();
+            mCommunicationDeviceTracker = communicationDeviceTracker;
         }
     }
 
@@ -398,12 +402,7 @@
     }
 
     public void disconnectAudio() {
-        disconnectSco();
-        clearLeAudioCommunicationDevice();
-        clearHearingAidCommunicationDevice();
-    }
-
-    public void disconnectSco() {
+        mCommunicationDeviceTracker.clearBtCommunicationDevice();
         if (mBluetoothHeadset == null) {
             Log.w(this, "Trying to disconnect audio but no headset service exists.");
         } else {
@@ -581,7 +580,8 @@
                  * Only after receiving ACTION_ACTIVE_DEVICE_CHANGED it is known that device that
                  * will be audio switched to is available to be choose as communication device */
                 if (!switchingBtDevices) {
-                    return setLeAudioCommunicationDevice();
+                    return mCommunicationDeviceTracker.setCommunicationDevice(
+                            AudioDeviceInfo.TYPE_BLE_HEADSET, device);
                 }
 
                 return true;
@@ -600,7 +600,8 @@
                  * Only after receiving ACTION_ACTIVE_DEVICE_CHANGED it is known that device that
                  * will be audio switched to is available to be choose as communication device */
                 if (!switchingBtDevices) {
-                    return setHearingAidCommunicationDevice();
+                    return mCommunicationDeviceTracker.setCommunicationDevice(
+                            AudioDeviceInfo.TYPE_HEARING_AID, null);
                 }
 
                 return true;
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
index 7966f73..556a8a5 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
@@ -23,6 +23,7 @@
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothLeAudio;
 import android.content.Context;
+import android.media.AudioDeviceInfo;
 import android.os.Message;
 import android.telecom.Log;
 import android.telecom.Logging.Session;
@@ -33,6 +34,7 @@
 import com.android.internal.util.IState;
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
+import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
 import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.Timeouts;
 
@@ -469,15 +471,18 @@
     private BluetoothDevice mHearingAidActiveDeviceCache = null;
     private BluetoothDevice mLeAudioActiveDeviceCache = null;
     private BluetoothDevice mMostRecentlyReportedActiveDevice = null;
+    private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
 
     public BluetoothRouteManager(Context context, TelecomSystem.SyncRoot lock,
-            BluetoothDeviceManager deviceManager, Timeouts.Adapter timeoutsAdapter) {
+            BluetoothDeviceManager deviceManager, Timeouts.Adapter timeoutsAdapter,
+            CallAudioCommunicationDeviceTracker communicationDeviceTracker) {
         super(BluetoothRouteManager.class.getSimpleName());
         mContext = context;
         mLock = lock;
         mDeviceManager = deviceManager;
         mDeviceManager.setBluetoothRouteManager(this);
         mTimeoutsAdapter = timeoutsAdapter;
+        mCommunicationDeviceTracker = communicationDeviceTracker;
 
         mAudioOffState = new AudioOffState();
         addState(mAudioOffState);
@@ -621,12 +626,14 @@
         if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO) {
             mLeAudioActiveDeviceCache = device;
             if (device == null) {
-                mDeviceManager.clearLeAudioCommunicationDevice();
+                mCommunicationDeviceTracker.clearCommunicationDevice(
+                        AudioDeviceInfo.TYPE_BLE_HEADSET);
             }
         } else if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID) {
             mHearingAidActiveDeviceCache = device;
             if (device == null) {
-                mDeviceManager.clearHearingAidCommunicationDevice();
+                mCommunicationDeviceTracker.clearCommunicationDevice(
+                        AudioDeviceInfo.TYPE_HEARING_AID);
             }
         } else if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEADSET) {
             mHfpActiveDeviceCache = device;
@@ -798,7 +805,8 @@
         }
 
         if (bluetoothHearingAid != null) {
-            if (mDeviceManager.isHearingAidSetAsCommunicationDevice()) {
+            if (mCommunicationDeviceTracker.isAudioDeviceSetForType(
+                    AudioDeviceInfo.TYPE_HEARING_AID)) {
                 for (BluetoothDevice device : bluetoothAdapter.getActiveDevices(
                         BluetoothProfile.HEARING_AID)) {
                     if (device != null) {
@@ -811,7 +819,8 @@
         }
 
         if (bluetoothLeAudio != null) {
-            if (mDeviceManager.isLeAudioCommunicationDevice()) {
+            if (mCommunicationDeviceTracker.isAudioDeviceSetForType(
+                    AudioDeviceInfo.TYPE_BLE_HEADSET)) {
                 for (BluetoothDevice device : bluetoothAdapter.getActiveDevices(
                         BluetoothProfile.LE_AUDIO)) {
                     if (device != null) {
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
index e5fe971..ab002c9 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
@@ -25,10 +25,12 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.media.AudioDeviceInfo;
 import android.telecom.Log;
 import android.telecom.Logging.Session;
 
 import com.android.internal.os.SomeArgs;
+import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
 
 import static com.android.server.telecom.bluetooth.BluetoothRouteManager.BT_AUDIO_IS_ON;
 import static com.android.server.telecom.bluetooth.BluetoothRouteManager.BT_AUDIO_LOST;
@@ -53,6 +55,7 @@
     private boolean mIsInCall = false;
     private final BluetoothRouteManager mBluetoothRouteManager;
     private final BluetoothDeviceManager mBluetoothDeviceManager;
+    private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
 
     public void onReceive(Context context, Intent intent) {
         Log.startSession("BSR.oR");
@@ -184,7 +187,8 @@
                     /* In Le Audio case, once device got Active, the Telecom needs to make sure it
                      * is set as communication device before we can say that BT_AUDIO_IS_ON
                      */
-                    if (!mBluetoothDeviceManager.setLeAudioCommunicationDevice()) {
+                    if (!mCommunicationDeviceTracker.setCommunicationDevice(
+                            AudioDeviceInfo.TYPE_BLE_HEADSET, device)) {
                         Log.w(LOG_TAG,
                                 "Device %s cannot be use as LE audio communication device.",
                                 device);
@@ -192,7 +196,8 @@
                     }
                 } else {
                     /* deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID */
-                    if (!mBluetoothDeviceManager.setHearingAidCommunicationDevice()) {
+                    if (!mCommunicationDeviceTracker.setCommunicationDevice(
+                            AudioDeviceInfo.TYPE_HEARING_AID, null)) {
                         Log.w(LOG_TAG,
                                 "Device %s cannot be use as hearing aid communication device.",
                                 device);
@@ -209,9 +214,11 @@
     }
 
     public BluetoothStateReceiver(BluetoothDeviceManager deviceManager,
-            BluetoothRouteManager routeManager) {
+            BluetoothRouteManager routeManager,
+            CallAudioCommunicationDeviceTracker communicationDeviceTracker) {
         mBluetoothDeviceManager = deviceManager;
         mBluetoothRouteManager = routeManager;
+        mCommunicationDeviceTracker = communicationDeviceTracker;
     }
 
     public void setIsInCall(boolean isInCall) {
diff --git a/tests/src/com/android/server/telecom/tests/BasicCallTests.java b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
index 9047da3..4f95cb3 100644
--- a/tests/src/com/android/server/telecom/tests/BasicCallTests.java
+++ b/tests/src/com/android/server/telecom/tests/BasicCallTests.java
@@ -642,8 +642,8 @@
                 .getCallAudioRouteStateMachine().getHandler(), TEST_TIMEOUT);
         ArgumentCaptor<AudioDeviceInfo> infoArgumentCaptor =
                 ArgumentCaptor.forClass(AudioDeviceInfo.class);
-        verify(audioManager, timeout(TEST_TIMEOUT)).setCommunicationDevice(
-                infoArgumentCaptor.capture());
+        verify(audioManager, timeout(TEST_TIMEOUT).atLeast(1))
+                .setCommunicationDevice(infoArgumentCaptor.capture());
         assertEquals(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, infoArgumentCaptor.getValue().getType());
         mInCallServiceFixtureX.mInCallAdapter.setAudioRoute(CallAudioState.ROUTE_EARPIECE, null);
         waitForHandlerAction(mTelecomSystem.getCallsManager().getCallAudioManager()
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
index c37d136..344469d 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothDeviceManagerTest.java
@@ -24,13 +24,13 @@
 import android.bluetooth.BluetoothHearingAid;
 import android.bluetooth.BluetoothLeAudio;
 import android.bluetooth.BluetoothProfile;
-import android.content.BroadcastReceiver;
 import android.content.Intent;
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
 import android.os.Parcel;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
 import com.android.server.telecom.bluetooth.BluetoothDeviceManager;
 import com.android.server.telecom.bluetooth.BluetoothRouteManager;
 import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
@@ -53,7 +53,6 @@
 import static org.mockito.Matchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -76,6 +75,7 @@
     BluetoothDeviceManager mBluetoothDeviceManager;
     BluetoothProfile.ServiceListener serviceListenerUnderTest;
     BluetoothStateReceiver receiverUnderTest;
+    CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
     ArgumentCaptor<BluetoothLeAudio.Callback> leAudioCallbacksTest;
 
     private BluetoothDevice device1;
@@ -103,8 +103,11 @@
         when(mBluetoothHearingAid.getHiSyncId(device4)).thenReturn(100L);
 
         mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
-        mBluetoothDeviceManager = new BluetoothDeviceManager(mContext, mAdapter);
+        mCommunicationDeviceTracker = new CallAudioCommunicationDeviceTracker(mContext);
+        mBluetoothDeviceManager = new BluetoothDeviceManager(mContext, mAdapter,
+                mCommunicationDeviceTracker);
         mBluetoothDeviceManager.setBluetoothRouteManager(mRouteManager);
+        mCommunicationDeviceTracker.setBluetoothRouteManager(mRouteManager);
 
         mockAudioManager = mContext.getSystemService(AudioManager.class);
 
@@ -114,7 +117,8 @@
                 serviceCaptor.capture(), eq(BluetoothProfile.HEADSET));
         serviceListenerUnderTest = serviceCaptor.getValue();
 
-        receiverUnderTest = new BluetoothStateReceiver(mBluetoothDeviceManager, mRouteManager);
+        receiverUnderTest = new BluetoothStateReceiver(mBluetoothDeviceManager,
+                mRouteManager, mCommunicationDeviceTracker);
 
         mBluetoothDeviceManager.setHeadsetServiceForTesting(mBluetoothHeadset);
         mBluetoothDeviceManager.setHearingAidServiceForTesting(mBluetoothHearingAid);
@@ -408,8 +412,8 @@
         when(mAdapter.setActiveDevice(nullable(BluetoothDevice.class),
                 eq(BluetoothAdapter.ACTIVE_DEVICE_ALL))).thenReturn(true);
 
-        AudioDeviceInfo mockAudioDeviceInfo = mock(AudioDeviceInfo.class);
-        when(mockAudioDeviceInfo.getType()).thenReturn(AudioDeviceInfo.TYPE_HEARING_AID);
+        AudioDeviceInfo mockAudioDeviceInfo = createMockAudioDeviceInfo(device5.getAddress(),
+                AudioDeviceInfo.TYPE_HEARING_AID);
         List<AudioDeviceInfo> devices = new ArrayList<>();
         devices.add(mockAudioDeviceInfo);
 
@@ -443,8 +447,8 @@
         when(mAdapter.setActiveDevice(nullable(BluetoothDevice.class),
                 eq(BluetoothAdapter.ACTIVE_DEVICE_ALL))).thenReturn(true);
 
-        AudioDeviceInfo mockAudioDeviceInfo = mock(AudioDeviceInfo.class);
-        when(mockAudioDeviceInfo.getType()).thenReturn(AudioDeviceInfo.TYPE_BLE_HEADSET);
+        AudioDeviceInfo mockAudioDeviceInfo = createMockAudioDeviceInfo(device5.getAddress(),
+                AudioDeviceInfo.TYPE_BLE_HEADSET);
         List<AudioDeviceInfo> devices = new ArrayList<>();
         devices.add(mockAudioDeviceInfo);
 
@@ -499,6 +503,88 @@
 
     @SmallTest
     @Test
+    public void testConnectMultipleLeAudioDevices() {
+        receiverUnderTest.setIsInCall(true);
+        receiverUnderTest.onReceive(mContext,
+                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1,
+                        BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
+        leAudioCallbacksTest.getValue().onGroupNodeAdded(device1, 1);
+        receiverUnderTest.onReceive(mContext,
+                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device2,
+                        BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
+        leAudioCallbacksTest.getValue().onGroupNodeAdded(device2, 1);
+        when(mAdapter.setActiveDevice(nullable(BluetoothDevice.class),
+                eq(BluetoothAdapter.ACTIVE_DEVICE_ALL))).thenReturn(true);
+
+        List<AudioDeviceInfo> devices = new ArrayList<>();
+        AudioDeviceInfo leAudioDevice1 = createMockAudioDeviceInfo(device1.getAddress(),
+                AudioDeviceInfo.TYPE_BLE_HEADSET);
+        AudioDeviceInfo leAudioDevice2 = createMockAudioDeviceInfo(device2.getAddress(),
+                AudioDeviceInfo.TYPE_BLE_HEADSET);
+        devices.add(leAudioDevice1);
+        devices.add(leAudioDevice2);
+
+        when(mockAudioManager.getAvailableCommunicationDevices())
+                .thenReturn(devices);
+        when(mockAudioManager.setCommunicationDevice(any(AudioDeviceInfo.class)))
+                .thenReturn(true);
+
+        // Connect LE audio device
+        mBluetoothDeviceManager.connectAudio(device1.getAddress(), false);
+        verify(mAdapter).setActiveDevice(device1, BluetoothAdapter.ACTIVE_DEVICE_ALL);
+        verify(mBluetoothHeadset, never()).connectAudio();
+        verify(mAdapter, never()).setActiveDevice(nullable(BluetoothDevice.class),
+                eq(BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL));
+        // Verify that we set the communication device for device 1
+        verify(mockAudioManager).setCommunicationDevice(leAudioDevice1);
+
+        // Change active device to other LE audio device
+        receiverUnderTest.onReceive(mContext, buildActiveDeviceChangeActionIntent(device2,
+                BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
+
+        // Verify call to clearLeAudioCommunicationDevice
+        verify(mRouteManager).onAudioLost(eq(DEVICE_ADDRESS_1));
+        // Verify that we set the communication device for device2
+        verify(mockAudioManager).setCommunicationDevice(leAudioDevice2);
+    }
+
+    @SmallTest
+    @Test
+    public void testClearCommunicationDeviceOnActiveDeviceChange() {
+        receiverUnderTest.setIsInCall(true);
+//        receiverUnderTest.onReceive(mContext,
+//                buildConnectionActionIntent(BluetoothHeadset.STATE_CONNECTED, device1,
+//                        BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
+//        leAudioCallbacksTest.getValue().onGroupNodeAdded(device1, 1);
+//        when(mAdapter.setActiveDevice(nullable(BluetoothDevice.class),
+//                eq(BluetoothAdapter.ACTIVE_DEVICE_ALL))).thenReturn(true);
+
+        List<AudioDeviceInfo> devices = new ArrayList<>();
+        AudioDeviceInfo leAudioDevice1 = createMockAudioDeviceInfo(device1.getAddress(),
+                AudioDeviceInfo.TYPE_BLE_HEADSET);
+        devices.add(leAudioDevice1);
+
+        when(mockAudioManager.getAvailableCommunicationDevices())
+                .thenReturn(devices);
+        when(mockAudioManager.setCommunicationDevice(any(AudioDeviceInfo.class)))
+                .thenReturn(true);
+
+        // Pretend that the speaker device is currently the requested device set for communication.
+        // This test ensures that the set/clear communication logic for audio switching in/out of BT
+        // is properly working when the receiver processes an active device change intent.
+        mCommunicationDeviceTracker.setTestCommunicationDevice(TYPE_BUILTIN_SPEAKER);
+
+        // Notify that LE audio device has been turned on
+        receiverUnderTest.onReceive(mContext, buildActiveDeviceChangeActionIntent(device1,
+                BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO));
+        // Verify call to clear speaker communication device
+        verify(mockAudioManager).clearCommunicationDevice();
+        // Verify that LE audio communication device was set after clearing the speaker device
+        verify(mockAudioManager).setCommunicationDevice(leAudioDevice1);
+    }
+
+    @SmallTest
+    @Test
     public void testClearHearingAidCommunicationDevice() {
         AudioDeviceInfo mockAudioDeviceInfo = mock(AudioDeviceInfo.class);
         when(mockAudioDeviceInfo.getAddress()).thenReturn(DEVICE_ADDRESS_1);
@@ -511,11 +597,34 @@
         when(mockAudioManager.setCommunicationDevice(eq(mockAudioDeviceInfo)))
                 .thenReturn(true);
 
-        mBluetoothDeviceManager.setHearingAidCommunicationDevice();
+        mCommunicationDeviceTracker.setCommunicationDevice(AudioDeviceInfo.TYPE_HEARING_AID, null);
         when(mockAudioManager.getCommunicationDevice()).thenReturn(mSpeakerInfo);
-        mBluetoothDeviceManager.clearHearingAidCommunicationDevice();
+        mCommunicationDeviceTracker.clearCommunicationDevice(AudioDeviceInfo.TYPE_HEARING_AID);
         verify(mRouteManager).onAudioLost(eq(DEVICE_ADDRESS_1));
-        assertFalse(mBluetoothDeviceManager.isHearingAidSetAsCommunicationDevice());
+        assertFalse(mCommunicationDeviceTracker.isAudioDeviceSetForType(
+                AudioDeviceInfo.TYPE_HEARING_AID));
+    }
+
+    @SmallTest
+    @Test
+    public void testClearLeAudioCommunicationDevice() {
+        AudioDeviceInfo mockAudioDeviceInfo = createMockAudioDeviceInfo(DEVICE_ADDRESS_1,
+                AudioDeviceInfo.TYPE_BLE_HEADSET);
+        List<AudioDeviceInfo> devices = new ArrayList<>();
+        devices.add(mockAudioDeviceInfo);
+
+        when(mockAudioManager.getAvailableCommunicationDevices())
+                .thenReturn(devices);
+        when(mockAudioManager.setCommunicationDevice(eq(mockAudioDeviceInfo)))
+                .thenReturn(true);
+
+        mCommunicationDeviceTracker.setCommunicationDevice(
+                AudioDeviceInfo.TYPE_BLE_HEADSET, device1);
+        when(mockAudioManager.getCommunicationDevice()).thenReturn(mSpeakerInfo);
+        mCommunicationDeviceTracker.clearCommunicationDevice(AudioDeviceInfo.TYPE_BLE_HEADSET);
+        verify(mRouteManager).onAudioLost(eq(DEVICE_ADDRESS_1));
+        assertFalse(mCommunicationDeviceTracker.isAudioDeviceSetForType(
+                AudioDeviceInfo.TYPE_BLE_HEADSET));
     }
 
     @SmallTest
@@ -541,6 +650,15 @@
         assertTrue(mBluetoothDeviceManager.isInbandRingingEnabled());
     }
 
+    private AudioDeviceInfo createMockAudioDeviceInfo(String address, int audioType) {
+        AudioDeviceInfo mockAudioDeviceInfo = mock(AudioDeviceInfo.class);
+        when(mockAudioDeviceInfo.getType()).thenReturn(audioType);
+        if (address != null) {
+            when(mockAudioDeviceInfo.getAddress()).thenReturn(address);
+        }
+        return mockAudioDeviceInfo;
+    }
+
     private Intent buildConnectionActionIntent(int state, BluetoothDevice device, int deviceType) {
         String intentString;
 
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
index 1a6fb88..2b5e5ac 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
@@ -29,6 +29,7 @@
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.internal.os.SomeArgs;
+import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
 import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.Timeouts;
 import com.android.server.telecom.bluetooth.BluetoothDeviceManager;
@@ -71,6 +72,7 @@
     @Mock private BluetoothLeAudio mBluetoothLeAudio;
     @Mock private Timeouts.Adapter mTimeoutsAdapter;
     @Mock private BluetoothRouteManager.BluetoothStateListener mListener;
+    @Mock private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
 
     @Override
     @Before
@@ -175,7 +177,8 @@
             BluetoothDevice initialDevice) {
         resetMocks();
         BluetoothRouteManager sm = new BluetoothRouteManager(mContext,
-                new TelecomSystem.SyncRoot() { }, mDeviceManager, mTimeoutsAdapter);
+                new TelecomSystem.SyncRoot() { }, mDeviceManager,
+                mTimeoutsAdapter, mCommunicationDeviceTracker);
         sm.setListener(mListener);
         sm.setInitialStateForTesting(initialState, initialDevice);
         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java b/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
index 5eecccc..b31ad5f 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothRouteTransitionTests.java
@@ -28,6 +28,7 @@
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.internal.os.SomeArgs;
+import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
 import com.android.server.telecom.TelecomSystem;
 import com.android.server.telecom.Timeouts;
 import com.android.server.telecom.bluetooth.BluetoothDeviceManager;
@@ -263,6 +264,7 @@
     @Mock private BluetoothLeAudio mBluetoothLeAudio;
     @Mock private Timeouts.Adapter mTimeoutsAdapter;
     @Mock private BluetoothRouteManager.BluetoothStateListener mListener;
+    @Mock private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
 
     @Override
     @Before
@@ -413,7 +415,8 @@
         when(mTimeoutsAdapter.getBluetoothPendingTimeoutMillis(
                 nullable(ContentResolver.class))).thenReturn(100000L);
         BluetoothRouteManager sm = new BluetoothRouteManager(mContext,
-                new TelecomSystem.SyncRoot() { }, mDeviceManager, mTimeoutsAdapter);
+                new TelecomSystem.SyncRoot() { }, mDeviceManager,
+                mTimeoutsAdapter, mCommunicationDeviceTracker);
         sm.setListener(mListener);
         sm.setInitialStateForTesting(initialState, initialDevice);
         waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
index 569c487..5b3d6f1 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
@@ -29,6 +29,7 @@
 import android.test.suitebuilder.annotation.MediumTest;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
 import com.android.server.telecom.bluetooth.BluetoothRouteManager;
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallAudioRouteStateMachine;
@@ -50,6 +51,7 @@
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -100,6 +102,7 @@
     private AudioManager mockAudioManager;
     private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() { };
     private HandlerThread mThreadHandler;
+    CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
 
     @Override
     @Before
@@ -110,6 +113,8 @@
         mThreadHandler.start();
         mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
         mockAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        mCommunicationDeviceTracker = new CallAudioCommunicationDeviceTracker(mContext);
+        mCommunicationDeviceTracker.setBluetoothRouteManager(mockBluetoothRouteManager);
 
         mAudioServiceFactory = new CallAudioManager.AudioServiceFactory() {
             @Override
@@ -154,7 +159,8 @@
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT,
                 mThreadHandler.getLooper(),
-                Runnable::run /** do async stuff sync for test purposes */);
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker);
 
         // 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
@@ -174,7 +180,8 @@
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT,
                 mThreadHandler.getLooper(),
-                Runnable::run /** do async stuff sync for test purposes */);
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker);
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         Set<Call> trackedCalls = new HashSet<>(Arrays.asList(fakeCall, fakeSelfManagedCall));
@@ -220,7 +227,8 @@
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
                 mThreadHandler.getLooper(),
-                Runnable::run /** do async stuff sync for test purposes */);
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker);
         stateMachine.setCallAudioManager(mockCallAudioManager);
         CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER);
@@ -264,7 +272,8 @@
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
                 mThreadHandler.getLooper(),
-                Runnable::run /** do async stuff sync for test purposes */);
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker);
 
         when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
         when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true);
@@ -310,7 +319,8 @@
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
                 mThreadHandler.getLooper(),
-                Runnable::run /** do async stuff sync for test purposes */);
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker);
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
@@ -355,7 +365,8 @@
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
                 mThreadHandler.getLooper(),
-                Runnable::run /** do async stuff sync for test purposes */);
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker);
         stateMachine.setCallAudioManager(mockCallAudioManager);
         Collection<BluetoothDevice> availableDevices = Collections.singleton(bluetoothDevice1);
 
@@ -434,7 +445,8 @@
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
                 mThreadHandler.getLooper(),
-                Runnable::run /** do async stuff sync for test purposes */);
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker);
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
@@ -471,7 +483,8 @@
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
                 mThreadHandler.getLooper(),
-                Runnable::run /** do async stuff sync for test purposes */);
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker);
         stateMachine.setCallAudioManager(mockCallAudioManager);
         setInBandRing(false);
         when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
@@ -512,7 +525,10 @@
                 .thenReturn(bluetoothDevice1);
         stateMachine.sendMessage(CallAudioRouteStateMachine.BT_AUDIO_CONNECTED);
         waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
-        verify(mockCallAudioManager, times(1)).onRingerModeChange();
+        // It is possible that this will be called twice from ActiveBluetoothRoute#enter. The extra
+        // call to setBluetoothOn will trigger BT_AUDIO_CONNECTED, which also ends up invoking
+        // CallAudioManager#onRingerModeChange.
+        verify(mockCallAudioManager, atLeastOnce()).onRingerModeChange();
     }
 
     @SmallTest
@@ -527,7 +543,8 @@
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
                 mThreadHandler.getLooper(),
-                Runnable::run /** do async stuff sync for test purposes */);
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker);
         stateMachine.setCallAudioManager(mockCallAudioManager);
         List<BluetoothDevice> availableDevices =
                 Arrays.asList(bluetoothDevice1, bluetoothDevice2, bluetoothDevice3);
@@ -578,7 +595,8 @@
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
                 mThreadHandler.getLooper(),
-                Runnable::run /** do async stuff sync for test purposes */);
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker);
         stateMachine.setCallAudioManager(mockCallAudioManager);
         when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false);
         CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
@@ -610,7 +628,8 @@
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
                 mThreadHandler.getLooper(),
-                Runnable::run /** do async stuff sync for test purposes */);
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker);
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false);
@@ -645,7 +664,8 @@
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
                 mThreadHandler.getLooper(),
-                Runnable::run /** do async stuff sync for test purposes */);
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker);
         stateMachine.setCallAudioManager(mockCallAudioManager);
         List<BluetoothDevice> availableDevices =
                 Arrays.asList(bluetoothDevice1, bluetoothDevice2);
@@ -678,6 +698,106 @@
 
     @SmallTest
     @Test
+    public void testSetAndClearEarpieceCommunicationDevice() {
+        CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+                mContext,
+                mockCallsManager,
+                mockBluetoothRouteManager,
+                mockWiredHeadsetManager,
+                mockStatusBarNotifier,
+                mAudioServiceFactory,
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker);
+        stateMachine.setCallAudioManager(mockCallAudioManager);
+
+        AudioDeviceInfo earpiece = mock(AudioDeviceInfo.class);
+        when(earpiece.getType()).thenReturn(AudioDeviceInfo.TYPE_BUILTIN_EARPIECE);
+        when(earpiece.getAddress()).thenReturn("");
+        List<AudioDeviceInfo> devices = new ArrayList<>();
+        devices.add(earpiece);
+
+        when(mockAudioManager.getAvailableCommunicationDevices())
+                .thenReturn(devices);
+        when(mockAudioManager.setCommunicationDevice(eq(earpiece)))
+                .thenReturn(true);
+        when(mockAudioManager.getCommunicationDevice()).thenReturn(earpiece);
+
+        CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER |
+                        CallAudioState.ROUTE_WIRED_HEADSET);
+        stateMachine.initialize(initState);
+
+        // Switch to active
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+                CallAudioRouteStateMachine.ACTIVE_FOCUS);
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+
+        // Make sure that we've successfully switched to the active earpiece and that we set the
+        // communication device.
+        assertTrue(stateMachine.isInActiveState());
+        ArgumentCaptor<AudioDeviceInfo> infoArgumentCaptor = ArgumentCaptor.forClass(
+                AudioDeviceInfo.class);
+        verify(mockAudioManager).setCommunicationDevice(infoArgumentCaptor.capture());
+        assertEquals(AudioDeviceInfo.TYPE_BUILTIN_EARPIECE,
+                infoArgumentCaptor.getValue().getType());
+
+        // Route earpiece to speaker
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_SPEAKER,
+                CallAudioRouteStateMachine.SPEAKER_ON);
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+
+        // Assert that communication device was cleared
+        verify(mockAudioManager).clearCommunicationDevice();
+    }
+
+    @SmallTest
+    @Test
+    public void testSetAndClearWiredHeadsetCommunicationDevice() {
+        verifySetAndClearHeadsetCommunicationDevice(AudioDeviceInfo.TYPE_WIRED_HEADSET);
+    }
+
+    @SmallTest
+    @Test
+    public void testSetAndClearUsbHeadsetCommunicationDevice() {
+        verifySetAndClearHeadsetCommunicationDevice(AudioDeviceInfo.TYPE_USB_HEADSET);
+    }
+
+    @SmallTest
+    @Test
+    public void testActiveFocusRouteSwitchFromQuiescentBluetooth() {
+        CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+                mContext,
+                mockCallsManager,
+                mockBluetoothRouteManager,
+                mockWiredHeadsetManager,
+                mockStatusBarNotifier,
+                mAudioServiceFactory,
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker);
+        stateMachine.setCallAudioManager(mockCallAudioManager);
+
+        // Start the route in quiescent and ensure that a switch to ACTIVE_FOCUS transitions to
+        // the corresponding active route even when there aren't any active BT devices available.
+        CallAudioState initState = new CallAudioState(false,
+                CallAudioState.ROUTE_BLUETOOTH,
+                CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_EARPIECE);
+        stateMachine.initialize(initState);
+
+        // Switch to active
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+                CallAudioRouteStateMachine.ACTIVE_FOCUS);
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+
+        // Make sure that we've successfully switched to the active route on BT
+        assertTrue(stateMachine.isInActiveState());
+    }
+
+    @SmallTest
+    @Test
     public void testInitializationWithEarpieceNoHeadsetNoBluetooth() {
         CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER);
@@ -761,7 +881,8 @@
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
                 mThreadHandler.getLooper(),
-                Runnable::run /** do async stuff sync for test purposes */);
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker);
         stateMachine.initialize();
         assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
     }
@@ -778,7 +899,8 @@
                 mAudioServiceFactory,
                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
                 mThreadHandler.getLooper(),
-                Runnable::run /** do async stuff sync for test purposes */);
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker);
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
@@ -816,7 +938,8 @@
                 mAudioServiceFactory,
                 earpieceControl,
                 mThreadHandler.getLooper(),
-                Runnable::run /** do async stuff sync for test purposes */);
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker);
         stateMachine.initialize();
         assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
     }
@@ -856,4 +979,58 @@
         doNothing().when(mockConnectionServiceWrapper).onCallAudioStateChanged(any(Call.class),
                 any(CallAudioState.class));
     }
+
+    private void verifySetAndClearHeadsetCommunicationDevice(int audioType) {
+        CallAudioRouteStateMachine stateMachine = new CallAudioRouteStateMachine(
+                mContext,
+                mockCallsManager,
+                mockBluetoothRouteManager,
+                mockWiredHeadsetManager,
+                mockStatusBarNotifier,
+                mAudioServiceFactory,
+                CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
+                mThreadHandler.getLooper(),
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker);
+        stateMachine.setCallAudioManager(mockCallAudioManager);
+
+        AudioDeviceInfo headset = mock(AudioDeviceInfo.class);
+        when(headset.getType()).thenReturn(audioType);
+        when(headset.getAddress()).thenReturn("");
+        List<AudioDeviceInfo> devices = new ArrayList<>();
+        devices.add(headset);
+
+        when(mockAudioManager.getAvailableCommunicationDevices())
+                .thenReturn(devices);
+        when(mockAudioManager.setCommunicationDevice(eq(headset)))
+                .thenReturn(true);
+        when(mockAudioManager.getCommunicationDevice()).thenReturn(headset);
+
+        CallAudioState initState = new CallAudioState(false,
+                CallAudioState.ROUTE_WIRED_HEADSET,
+                CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_EARPIECE);
+        stateMachine.initialize(initState);
+
+        // Switch to active
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+                CallAudioRouteStateMachine.ACTIVE_FOCUS);
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+
+        // Make sure that we've successfully switched to the active headset and that we set the
+        // communication device.
+        assertTrue(stateMachine.isInActiveState());
+        ArgumentCaptor<AudioDeviceInfo> infoArgumentCaptor = ArgumentCaptor.forClass(
+                AudioDeviceInfo.class);
+        verify(mockAudioManager).setCommunicationDevice(infoArgumentCaptor.capture());
+        assertEquals(audioType, infoArgumentCaptor.getValue().getType());
+
+        // Route out of headset route
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.SWITCH_FOCUS,
+                CallAudioRouteStateMachine.ACTIVE_FOCUS);
+        stateMachine.sendMessageWithSessionInfo(CallAudioRouteStateMachine.USER_SWITCH_EARPIECE);
+        waitForHandlerAction(stateMachine.getHandler(), TEST_TIMEOUT);
+
+        // Assert that communication device was cleared
+        verify(mockAudioManager).clearCommunicationDevice();
+    }
 }
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
index cf684de..804ef17 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
@@ -20,6 +20,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.ArgumentMatchers.same;
+import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
@@ -40,6 +41,7 @@
 import android.test.suitebuilder.annotation.SmallTest;
 
 import com.android.server.telecom.Call;
+import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
 import com.android.server.telecom.CallAudioManager;
 import com.android.server.telecom.CallAudioRouteStateMachine;
 import com.android.server.telecom.CallsManager;
@@ -155,6 +157,7 @@
     @Mock StatusBarNotifier mockStatusBarNotifier;
     @Mock Call fakeCall;
     @Mock CallAudioManager mockCallAudioManager;
+    private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
     private CallAudioManager.AudioServiceFactory mAudioServiceFactory;
     private static final int TEST_TIMEOUT = 500;
     private AudioManager mockAudioManager;
@@ -174,6 +177,8 @@
         mHandlerThread.start();
         mContext = mComponentContextFixture.getTestDouble().getApplicationContext();
         mockAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        mCommunicationDeviceTracker = new CallAudioCommunicationDeviceTracker(mContext);
+        mCommunicationDeviceTracker.setBluetoothRouteManager(mockBluetoothRouteManager);
 
         mAudioServiceFactory = new CallAudioManager.AudioServiceFactory() {
             @Override
@@ -270,7 +275,8 @@
                 mAudioServiceFactory,
                 mParams.earpieceControl,
                 mHandlerThread.getLooper(),
-                Runnable::run /** do async stuff sync for test purposes */);
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker);
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         setupMocksForParams(stateMachine, mParams);
@@ -311,7 +317,7 @@
                 break;
             case ON:
                 if (mParams.expectedBluetoothDevice == null) {
-                    verify(mockBluetoothRouteManager).connectBluetoothAudio(null);
+                    verify(mockBluetoothRouteManager, atLeastOnce()).connectBluetoothAudio(null);
                 } else {
                     verify(mockBluetoothRouteManager).connectBluetoothAudio(
                             mParams.expectedBluetoothDevice.getAddress());
@@ -367,7 +373,8 @@
                 mAudioServiceFactory,
                 mParams.earpieceControl,
                 mHandlerThread.getLooper(),
-                Runnable::run /** do async stuff sync for test purposes */);
+                Runnable::run /** do async stuff sync for test purposes */,
+                mCommunicationDeviceTracker);
         stateMachine.setCallAudioManager(mockCallAudioManager);
 
         // Set up bluetooth and speakerphone state
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index 8a7d22c..ddb48ec 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -85,6 +85,7 @@
 import com.android.server.telecom.AsyncRingtonePlayer;
 import com.android.server.telecom.Call;
 import com.android.server.telecom.CallAnomalyWatchdog;
+import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
 import com.android.server.telecom.CallAudioManager;
 import com.android.server.telecom.CallAudioModeStateMachine;
 import com.android.server.telecom.CallAudioRouteStateMachine;
@@ -258,6 +259,7 @@
     @Mock private Ringer.AccessibilityManagerAdapter mAccessibilityManagerAdapter;
     @Mock private BlockedNumbersAdapter mBlockedNumbersAdapter;
     @Mock private PhoneCapability mPhoneCapability;
+    @Mock private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
     @Mock private CallStreamingNotification mCallStreamingNotification;
 
     private CallsManager mCallsManager;
@@ -278,7 +280,7 @@
         when(mCallEndpointControllerFactory.create(any(), any(), any())).thenReturn(
                 mCallEndpointController);
         when(mCallAudioRouteStateMachineFactory.create(any(), any(), any(), any(), any(), any(),
-                anyInt(), any())).thenReturn(mCallAudioRouteStateMachine);
+                anyInt(), any(), any())).thenReturn(mCallAudioRouteStateMachine);
         when(mCallAudioModeStateMachineFactory.create(any(), any()))
                 .thenReturn(mCallAudioModeStateMachine);
         when(mClockProxy.currentTimeMillis()).thenReturn(System.currentTimeMillis());
@@ -330,6 +332,7 @@
                 mBlockedNumbersAdapter,
                 TransactionManager.getTestInstance(),
                 mEmergencyCallDiagnosticLogger,
+                mCommunicationDeviceTracker,
                 mCallStreamingNotification);
 
         when(mPhoneAccountRegistrar.getPhoneAccount(
diff --git a/tests/src/com/android/server/telecom/tests/RingbackPlayerTest.java b/tests/src/com/android/server/telecom/tests/RingbackPlayerTest.java
new file mode 100644
index 0000000..8de5e28
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/RingbackPlayerTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom.tests;
+
+import static org.junit.Assert.assertFalse;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
+
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.InCallTonePlayer;
+import com.android.server.telecom.RingbackPlayer;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+
+import java.util.concurrent.CountDownLatch;
+
+@RunWith(JUnit4.class)
+public class RingbackPlayerTest extends TelecomTestCase {
+    @Mock InCallTonePlayer.Factory mFactory;
+    @Mock Call mCall;
+    @Mock InCallTonePlayer mTonePlayer;
+
+    private RingbackPlayer mRingbackPlayer;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        when(mFactory.createPlayer(anyInt())).thenReturn(mTonePlayer);
+        mRingbackPlayer = new RingbackPlayer(mFactory);
+    }
+
+    @SmallTest
+    @Test
+    public void testPlayerSync() {
+        // make sure InCallTonePlayer try to start playing the tone after RingbackPlayer receives
+        // stop tone request.
+        CountDownLatch latch = new CountDownLatch(1);
+        doReturn(CallState.DIALING).when(mCall).getState();
+        doAnswer(x -> {
+            new Thread(() -> {
+                try {
+                    latch.await();
+                } catch (InterruptedException e) {
+                    // Ignore
+                }
+            }).start();
+            return true;
+        }).when(mTonePlayer).startTone();
+
+        mRingbackPlayer.startRingbackForCall(mCall);
+        mRingbackPlayer.stopRingbackForCall(mCall);
+        assertFalse(mRingbackPlayer.isRingbackPlaying());
+        latch.countDown();
+    }
+}
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index d013fae..33ce533 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -69,6 +69,7 @@
 
 import com.android.internal.telecom.IInCallAdapter;
 import com.android.server.telecom.AsyncRingtonePlayer;
+import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
 import com.android.server.telecom.CallAudioManager;
 import com.android.server.telecom.CallAudioModeStateMachine;
 import com.android.server.telecom.CallAudioRouteStateMachine;
@@ -214,6 +215,8 @@
     @Mock Ringer.AccessibilityManagerAdapter mAccessibilityManagerAdapter;
     @Mock
     BlockedNumbersAdapter mBlockedNumbersAdapter;
+    @Mock
+    CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
 
     final ComponentName mInCallServiceComponentNameX =
             new ComponentName(
@@ -517,7 +520,8 @@
                             StatusBarNotifier statusBarNotifier,
                             CallAudioManager.AudioServiceFactory audioServiceFactory,
                             int earpieceControl,
-                            Executor asyncTaskExecutor) {
+                            Executor asyncTaskExecutor,
+                            CallAudioCommunicationDeviceTracker communicationDeviceTracker) {
                         return new CallAudioRouteStateMachine(context,
                                 callsManager,
                                 bluetoothManager,
@@ -527,7 +531,8 @@
                                 // Force enable an earpiece for the end-to-end tests
                                 CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
                                 mHandlerThread.getLooper(),
-                                Runnable::run /* async tasks as now sync for testing! */);
+                                Runnable::run /* async tasks as now sync for testing! */,
+                                communicationDeviceTracker);
                     }
                 },
                 new CallAudioModeStateMachine.Factory() {