Merge "Telecom SCO new audio HAL support" into main
diff --git a/src/com/android/server/telecom/AudioRoute.java b/src/com/android/server/telecom/AudioRoute.java
index db734ff..9898ca0 100644
--- a/src/com/android/server/telecom/AudioRoute.java
+++ b/src/com/android/server/telecom/AudioRoute.java
@@ -28,6 +28,7 @@
 import android.bluetooth.BluetoothStatusCodes;
 import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
+import android.sysprop.BluetoothProperties;
 import android.telecom.Log;
 import android.util.Pair;
 
@@ -139,6 +140,7 @@
     private String mBluetoothAddress;
     private AudioDeviceInfo mInfo;
     private boolean mIsDestRouteForWatch;
+    private boolean mIsScoManagedByAudio;
     public static final Set<Integer> BT_AUDIO_DEVICE_INFO_TYPES = Set.of(
             AudioDeviceInfo.TYPE_BLE_HEADSET,
             AudioDeviceInfo.TYPE_BLE_SPEAKER,
@@ -265,7 +267,7 @@
                 boolean connectedBtAudio = connectBtAudio(pendingAudioRoute, device,
                         audioManager, bluetoothRouteManager);
                 // Special handling for SCO case.
-                if (mAudioRouteType == TYPE_BLUETOOTH_SCO) {
+                if (!mIsScoManagedByAudio && mAudioRouteType == TYPE_BLUETOOTH_SCO) {
                     // Set whether the dest route is for the watch
                     mIsDestRouteForWatch = bluetoothRouteManager.isWatch(device);
                     // Check if the communication device was set for the device, even if
@@ -308,6 +310,10 @@
                     result = audioManager.setCommunicationDevice(mInfo);
                     if (result) {
                         pendingAudioRoute.setCommunicationDeviceType(mAudioRouteType);
+                        if (mAudioRouteType == TYPE_BLUETOOTH_SCO && !isScoAudioConnected
+                                && mIsScoManagedByAudio) {
+                            pendingAudioRoute.addMessage(BT_AUDIO_CONNECTED, mBluetoothAddress);
+                        }
                     }
                     Log.i(this, "onDestRouteAsPendingRoute: route=%s, "
                             + "AudioManager#setCommunicationDevice(%s)=%b", this,
@@ -355,6 +361,9 @@
         mAudioRouteType = type;
         mBluetoothAddress = bluetoothAddress;
         mInfo = info;
+        // Indication that SCO is managed by audio (i.e. supports setCommunicationDevice).
+        mIsScoManagedByAudio = android.media.audio.Flags.scoManagedByAudio()
+                && BluetoothProperties.isScoManagedByAudioEnabled().orElse(false);
     }
 
     @Override
@@ -398,7 +407,7 @@
         boolean success = false;
         if (device != null) {
             success = bluetoothRouteManager.getDeviceManager()
-                    .connectAudio(device, mAudioRouteType);
+                    .connectAudio(device, mAudioRouteType, mIsScoManagedByAudio);
         }
 
         Log.i(this, "connectBtAudio: routeToConnectTo = %s, successful = %b",
@@ -429,8 +438,9 @@
         }
 
         int result = BluetoothStatusCodes.SUCCESS;
-        if (pendingAudioRoute.getCommunicationDeviceType() == TYPE_BLUETOOTH_SCO) {
-            Log.i(this, "clearCommunicationDevice: Disconnecting SCO device.");
+        if (pendingAudioRoute.getCommunicationDeviceType() == TYPE_BLUETOOTH_SCO
+                && !mIsScoManagedByAudio) {
+            Log.i(this, "Disconnecting SCO device via BluetoothHeadset.");
             result = bluetoothRouteManager.getDeviceManager().disconnectSco();
         } else {
             // Only clear communication device if the destination route will be inactive; route to
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
index 27f7f96..8d7c2bf 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothDeviceManager.java
@@ -906,7 +906,8 @@
      * @param type {@link AudioRoute.AudioRouteType} associated with the device.
      * @return {@code true} if device was successfully connected, {@code false} otherwise.
      */
-    public boolean connectAudio(BluetoothDevice device, @AudioRoute.AudioRouteType int type) {
+    public boolean connectAudio(BluetoothDevice device, @AudioRoute.AudioRouteType int type,
+            boolean isScoManagedByAudio) {
         String address = device.getAddress();
         int callProfile = BluetoothProfile.LE_AUDIO;
         if (type == TYPE_BLUETOOTH_SCO) {
@@ -924,7 +925,7 @@
         }
 
         if (callProfile == BluetoothProfile.LE_AUDIO
-                || callProfile == BluetoothProfile.HEARING_AID) {
+                || callProfile == BluetoothProfile.HEARING_AID || isScoManagedByAudio) {
             return mBluetoothAdapter.setActiveDevice(device, BluetoothAdapter.ACTIVE_DEVICE_ALL);
         } else if (callProfile == BluetoothProfile.HEADSET) {
             boolean success = mBluetoothAdapter.setActiveDevice(device,
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
index 1cea531..beddcbe 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothStateReceiver.java
@@ -40,6 +40,7 @@
 import android.content.IntentFilter;
 import android.media.AudioDeviceInfo;
 import android.os.Bundle;
+import android.sysprop.BluetoothProperties;
 import android.telecom.Log;
 import android.telecom.Logging.Session;
 import android.util.Pair;
@@ -50,7 +51,6 @@
 import com.android.server.telecom.CallAudioRouteAdapter;
 import com.android.server.telecom.CallAudioRouteController;
 import com.android.server.telecom.flags.FeatureFlags;
-import com.android.server.telecom.flags.Flags;
 
 public class BluetoothStateReceiver extends BroadcastReceiver {
     private static final String LOG_TAG = BluetoothStateReceiver.class.getSimpleName();
@@ -74,6 +74,7 @@
     private final BluetoothDeviceManager mBluetoothDeviceManager;
     private CallAudioCommunicationDeviceTracker mCommunicationDeviceTracker;
     private FeatureFlags mFeatureFlags;
+    private boolean mIsScoManagedByAudio;
     private CallAudioRouteAdapter mCallAudioRouteAdapter;
 
     public void onReceive(Context context, Intent intent) {
@@ -269,7 +270,8 @@
                 mCallAudioRouteAdapter.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT,
                         audioRouteType, device.getAddress());
                 if (deviceType == BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID
-                        || deviceType == BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO) {
+                        || deviceType == BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO
+                        || mIsScoManagedByAudio) {
                     if (!mBluetoothDeviceManager.setCommunicationDeviceForAddress(
                             device.getAddress())) {
                         Log.i(this, "handleActiveDeviceChanged: Failed to set "
@@ -286,11 +288,12 @@
                         }
                     } else {
                         // Track the currently set communication device.
-                        int routeType = deviceType == BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO
-                                ? AudioRoute.TYPE_BLUETOOTH_LE
-                                : AudioRoute.TYPE_BLUETOOTH_HA;
                         mCallAudioRouteAdapter.getPendingAudioRoute()
-                                .setCommunicationDeviceType(routeType);
+                                .setCommunicationDeviceType(audioRouteType);
+                        if (audioRouteType == AudioRoute.TYPE_BLUETOOTH_SCO) {
+                            mCallAudioRouteAdapter.getPendingAudioRoute()
+                                    .addMessage(BT_AUDIO_CONNECTED, device.getAddress());
+                        }
                     }
                 }
             }
@@ -379,6 +382,9 @@
         mBluetoothRouteManager = routeManager;
         mCommunicationDeviceTracker = communicationDeviceTracker;
         mFeatureFlags = featureFlags;
+        // Indication that SCO is managed by audio (i.e. supports setCommunicationDevice).
+        mIsScoManagedByAudio = android.media.audio.Flags.scoManagedByAudio()
+                && BluetoothProperties.isScoManagedByAudioEnabled().orElse(false);
     }
 
     public void setIsInCall(boolean isInCall) {
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
index 993ca55..fa1afbb 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteControllerTest.java
@@ -44,7 +44,6 @@
 import static com.android.server.telecom.CallAudioRouteAdapter.USER_SWITCH_HEADSET;
 import static com.android.server.telecom.CallAudioRouteAdapter.USER_SWITCH_SPEAKER;
 import static com.android.server.telecom.CallAudioRouteController.INCLUDE_BLUETOOTH_IN_BASELINE;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -108,39 +107,57 @@
 
 @RunWith(JUnit4.class)
 public class CallAudioRouteControllerTest extends TelecomTestCase {
-    private CallAudioRouteController mController;
-    @Mock WiredHeadsetManager mWiredHeadsetManager;
-    @Mock AudioManager mAudioManager;
-    @Mock AudioDeviceInfo mEarpieceDeviceInfo;
-    @Mock CallsManager mCallsManager;
-    @Mock CallAudioManager.AudioServiceFactory mAudioServiceFactory;
-    @Mock IAudioService mAudioService;
-    @Mock BluetoothRouteManager mBluetoothRouteManager;
-    @Mock BluetoothDeviceManager mBluetoothDeviceManager;
-    @Mock BluetoothAdapter mBluetoothAdapter;
-    @Mock StatusBarNotifier mockStatusBarNotifier;
-    @Mock AudioDeviceInfo mAudioDeviceInfo;
-    @Mock BluetoothLeAudio mBluetoothLeAudio;
-    @Mock CallAudioManager mCallAudioManager;
-    @Mock Call mCall;
-    @Mock private TelecomSystem.SyncRoot mLock;
-    @Mock private TelecomMetricsController mMockTelecomMetricsController;
-    private AudioRoute mEarpieceRoute;
-    private AudioRoute mSpeakerRoute;
-    private boolean mOverrideSpeakerToBus;
     private static final String BT_ADDRESS_1 = "00:00:00:00:00:01";
     private static final BluetoothDevice BLUETOOTH_DEVICE_1 =
             BluetoothRouteManagerTest.makeBluetoothDevice("00:00:00:00:00:01");
     private static final Set<BluetoothDevice> BLUETOOTH_DEVICES;
+    private static final int TEST_TIMEOUT = 500;
+
     static {
         BLUETOOTH_DEVICES = new HashSet<>();
         BLUETOOTH_DEVICES.add(BLUETOOTH_DEVICE_1);
     }
-    private static final int TEST_TIMEOUT = 500;
+
+    @Mock
+    WiredHeadsetManager mWiredHeadsetManager;
+    @Mock
+    AudioManager mAudioManager;
+    @Mock
+    AudioDeviceInfo mEarpieceDeviceInfo;
+    @Mock
+    CallsManager mCallsManager;
+    @Mock
+    CallAudioManager.AudioServiceFactory mAudioServiceFactory;
+    @Mock
+    IAudioService mAudioService;
+    @Mock
+    BluetoothRouteManager mBluetoothRouteManager;
+    @Mock
+    BluetoothDeviceManager mBluetoothDeviceManager;
+    @Mock
+    BluetoothAdapter mBluetoothAdapter;
+    @Mock
+    StatusBarNotifier mockStatusBarNotifier;
+    @Mock
+    AudioDeviceInfo mAudioDeviceInfo;
+    @Mock
+    BluetoothLeAudio mBluetoothLeAudio;
+    @Mock
+    CallAudioManager mCallAudioManager;
+    @Mock
+    Call mCall;
+    private CallAudioRouteController mController;
+    @Mock
+    private TelecomSystem.SyncRoot mLock;
+    @Mock
+    private TelecomMetricsController mMockTelecomMetricsController;
+    private AudioRoute mEarpieceRoute;
+    private AudioRoute mSpeakerRoute;
+    private boolean mOverrideSpeakerToBus;
     AudioRoute.Factory mAudioRouteFactory = new AudioRoute.Factory() {
         @Override
         public AudioRoute create(@AudioRoute.AudioRouteType int type, String bluetoothAddress,
-                                 AudioManager audioManager) {
+                AudioManager audioManager) {
             if (mOverrideSpeakerToBus && type == AudioRoute.TYPE_SPEAKER) {
                 type = AudioRoute.TYPE_BUS;
             }
@@ -154,7 +171,7 @@
         when(mWiredHeadsetManager.isPluggedIn()).thenReturn(false);
         when(mEarpieceDeviceInfo.getType()).thenReturn(AudioDeviceInfo.TYPE_BUILTIN_EARPIECE);
         when(mAudioManager.getDevices(eq(AudioManager.GET_DEVICES_OUTPUTS))).thenReturn(
-                new AudioDeviceInfo[] {
+                new AudioDeviceInfo[]{
                         mEarpieceDeviceInfo
                 });
         when(mAudioManager.getPreferredDeviceForStrategy(nullable(AudioProductStrategy.class)))
@@ -173,7 +190,8 @@
         when(mCallsManager.getLock()).thenReturn(mLock);
         when(mCallsManager.getForegroundCall()).thenReturn(mCall);
         when(mBluetoothRouteManager.getDeviceManager()).thenReturn(mBluetoothDeviceManager);
-        when(mBluetoothDeviceManager.connectAudio(any(BluetoothDevice.class), anyInt()))
+        when(mBluetoothDeviceManager.connectAudio(any(BluetoothDevice.class), anyInt(),
+                anyBoolean()))
                 .thenReturn(true);
         when(mBluetoothDeviceManager.getBluetoothAdapter()).thenReturn(mBluetoothAdapter);
         when(mBluetoothAdapter.getActiveDevices(anyInt())).thenReturn(List.of(BLUETOOTH_DEVICE_1));
@@ -221,7 +239,7 @@
     @Test
     public void testInitializeWithoutEarpiece() {
         when(mAudioManager.getDevices(eq(AudioManager.GET_DEVICES_OUTPUTS))).thenReturn(
-                new AudioDeviceInfo[] {});
+                new AudioDeviceInfo[]{});
 
         mController.initialize();
         assertEquals(mSpeakerRoute, mController.getCurrentRoute());
@@ -408,7 +426,7 @@
 
         mController.sendMessageWithSessionInfo(SWITCH_FOCUS, RINGING_FOCUS, 0);
         verify(mBluetoothDeviceManager, timeout(TEST_TIMEOUT))
-                .connectAudio(BLUETOOTH_DEVICE_1, AudioRoute.TYPE_BLUETOOTH_SCO);
+                .connectAudio(BLUETOOTH_DEVICE_1, AudioRoute.TYPE_BLUETOOTH_SCO, false);
         assertTrue(mController.isActive());
 
         mController.sendMessageWithSessionInfo(SWITCH_FOCUS, ACTIVE_FOCUS, 0);
@@ -643,7 +661,6 @@
         assertTrue(foundValid);
     }
 
-
     @SmallTest
     @Test
     public void testToggleMute() throws Exception {
@@ -740,7 +757,7 @@
         mController.sendMessageWithSessionInfo(DISCONNECT_WIRED_HEADSET);
         expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER
-                        | CallAudioState.ROUTE_BLUETOOTH, null , BLUETOOTH_DEVICES);
+                        | CallAudioState.ROUTE_BLUETOOTH, null, BLUETOOTH_DEVICES);
         verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
                 any(CallAudioState.class), eq(expectedState));
     }
@@ -794,7 +811,6 @@
         verifyDisconnectBluetoothDevice(AudioRoute.TYPE_BLUETOOTH_HA);
     }
 
-
     @SmallTest
     @Test
     public void testSwitchBetweenLeAndScoDevices() {
@@ -837,7 +853,8 @@
     @SmallTest
     @Test
     public void testFallbackWhenBluetoothConnectionFails() {
-        when(mBluetoothDeviceManager.connectAudio(any(BluetoothDevice.class), anyInt()))
+        when(mBluetoothDeviceManager.connectAudio(any(BluetoothDevice.class), anyInt(),
+                anyBoolean()))
                 .thenReturn(false);
 
         AudioDeviceInfo mockAudioDeviceInfo = mock(AudioDeviceInfo.class);
@@ -860,7 +877,7 @@
         mController.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT,
                 AudioRoute.TYPE_BLUETOOTH_SCO, scoDevice.getAddress());
         verify(mBluetoothDeviceManager, timeout(TEST_TIMEOUT))
-                .connectAudio(scoDevice, AudioRoute.TYPE_BLUETOOTH_SCO);
+                .connectAudio(scoDevice, AudioRoute.TYPE_BLUETOOTH_SCO, false);
         expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH
                         | CallAudioState.ROUTE_SPEAKER, BLUETOOTH_DEVICE_1, BLUETOOTH_DEVICES);
@@ -897,31 +914,31 @@
 
         mController.initialize();
         mController.sendMessageWithSessionInfo(BT_DEVICE_ADDED, AudioRoute.TYPE_BLUETOOTH_SCO,
-            BLUETOOTH_DEVICE_1);
+                BLUETOOTH_DEVICE_1);
         CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
-            CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH
-                | CallAudioState.ROUTE_SPEAKER, null, BLUETOOTH_DEVICES);
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH
+                        | CallAudioState.ROUTE_SPEAKER, null, BLUETOOTH_DEVICES);
         verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
-            any(CallAudioState.class), eq(expectedState));
+                any(CallAudioState.class), eq(expectedState));
 
         mController.sendMessageWithSessionInfo(SWITCH_FOCUS, RINGING_FOCUS, 0);
         assertFalse(mController.isActive());
 
         // BT device should be cached. Verify routing into BT device once focus becomes active.
         mController.sendMessageWithSessionInfo(USER_SWITCH_BLUETOOTH, 0,
-            BLUETOOTH_DEVICE_1.getAddress());
+                BLUETOOTH_DEVICE_1.getAddress());
         expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
-            CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH
-                | CallAudioState.ROUTE_SPEAKER, BLUETOOTH_DEVICE_1, BLUETOOTH_DEVICES);
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH
+                        | CallAudioState.ROUTE_SPEAKER, BLUETOOTH_DEVICE_1, BLUETOOTH_DEVICES);
         verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
-            any(CallAudioState.class), eq(expectedState));
+                any(CallAudioState.class), eq(expectedState));
         mController.sendMessageWithSessionInfo(SWITCH_FOCUS, ACTIVE_FOCUS, 0);
         mController.sendMessageWithSessionInfo(BT_AUDIO_CONNECTED, 0, BLUETOOTH_DEVICE_1);
         expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
-            CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH
-                | CallAudioState.ROUTE_SPEAKER, BLUETOOTH_DEVICE_1, BLUETOOTH_DEVICES);
+                CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH
+                        | CallAudioState.ROUTE_SPEAKER, BLUETOOTH_DEVICE_1, BLUETOOTH_DEVICES);
         verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
-            any(CallAudioState.class), eq(expectedState));
+                any(CallAudioState.class), eq(expectedState));
     }
 
     @SmallTest
@@ -1179,7 +1196,7 @@
                 watchDevice);
         CallAudioState expectedState = new CallAudioState(false, CallAudioState.ROUTE_BLUETOOTH,
                 CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER
-                | CallAudioState.ROUTE_BLUETOOTH, BLUETOOTH_DEVICE_1, BLUETOOTH_DEVICES);
+                        | CallAudioState.ROUTE_BLUETOOTH, BLUETOOTH_DEVICE_1, BLUETOOTH_DEVICES);
         verify(mCallsManager, timeout(TEST_TIMEOUT)).onCallAudioStateChanged(
                 any(CallAudioState.class), eq(expectedState));
 
@@ -1218,7 +1235,6 @@
         BLUETOOTH_DEVICES.remove(watchDevice);
     }
 
-
     @Test
     @SmallTest
     public void testAbandonCallAudioFocusAfterCallEnd() {
@@ -1265,7 +1281,7 @@
         mController.sendMessageWithSessionInfo(BT_ACTIVE_DEVICE_PRESENT, audioType, BT_ADDRESS_1);
         if (audioType == AudioRoute.TYPE_BLUETOOTH_SCO) {
             verify(mBluetoothDeviceManager, timeout(TEST_TIMEOUT))
-                    .connectAudio(BLUETOOTH_DEVICE_1, AudioRoute.TYPE_BLUETOOTH_SCO);
+                    .connectAudio(BLUETOOTH_DEVICE_1, AudioRoute.TYPE_BLUETOOTH_SCO, false);
             mController.sendMessageWithSessionInfo(BT_AUDIO_CONNECTED,
                     0, BLUETOOTH_DEVICE_1);
         } else {