Merge "Add bluetooth as a supported route when we actually eneted a bluetooth route." into main
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 28c85e1..c6f5e9c 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -318,18 +318,6 @@
              android:exported="false"
              android:process=":ui"/>
 
-        <service android:name=".components.BluetoothPhoneService"
-             android:singleUser="true"
-             android:process="system"
-             android:exported="true">
-            <intent-filter>
-                <action android:name="android.bluetooth.IBluetoothHeadsetPhone"/>
-            </intent-filter>
-            <intent-filter>
-                <action android:name="android.bluetooth.IBluetoothLeCallControlCallback" />
-            </intent-filter>
-        </service>
-
         <service android:name=".components.TelecomService"
              android:singleUser="true"
              android:process="system"
diff --git a/flags/Android.bp b/flags/Android.bp
index 4f43a52..af8b683 100644
--- a/flags/Android.bp
+++ b/flags/Android.bp
@@ -34,6 +34,7 @@
       "telecom_callaudiomodestatemachine_flags.aconfig",
       "telecom_calllog_flags.aconfig",
       "telecom_resolve_hidden_dependencies.aconfig",
+      "telecom_bluetoothroutemanager_flags.aconfig",
     ],
 }
 
diff --git a/flags/telecom_bluetoothroutemanager_flags.aconfig b/flags/telecom_bluetoothroutemanager_flags.aconfig
new file mode 100644
index 0000000..ddd8571
--- /dev/null
+++ b/flags/telecom_bluetoothroutemanager_flags.aconfig
@@ -0,0 +1,9 @@
+package: "com.android.server.telecom.flags"
+
+flag {
+  name: "use_actual_address_to_enter_connecting_state"
+  namespace: "telecom"
+  description: "Fix bugs that may add bluetooth device with null address."
+  bug: "306113816"
+}
+
diff --git a/src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java b/src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java
index 4af60f0..5fc2414 100644
--- a/src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java
+++ b/src/com/android/server/telecom/CallAudioCommunicationDeviceTracker.java
@@ -159,6 +159,11 @@
                 if (audioDeviceType == AudioDeviceInfo.TYPE_BLE_HEADSET) {
                     mBluetoothRouteManager.onAudioOn(mBtAudioDevice);
                 }
+            } else if (Flags.communicationDeviceProtectedByLock()) {
+                // Clear BT device if it's still stored. Handles race condition for when a non-BT
+                // device is set for communication shortly after a BT (LE) device is set for
+                // communication but the selection hasn't been cleared yet.
+                mBtAudioDevice = null;
             }
         }
         if (Flags.communicationDeviceProtectedByLock()) {
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index c11ee6e..07b048d 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -525,8 +525,6 @@
                         .validateAccountIconUserBoundary(icon, callingUserHandle));
             }
 
-            if (ConnectionServiceWrapper.this.mIsRemoteConnectionService) return;
-
             if (parcelableConference.getConnectElapsedTimeMillis() != 0
                     && mContext.checkCallingOrSelfPermission(MODIFY_PHONE_STATE)
                             != PackageManager.PERMISSION_GRANTED) {
@@ -941,9 +939,6 @@
         public void addExistingConnection(String callId, ParcelableConnection connection,
                 Session.Info sessionInfo) {
             Log.startSession(sessionInfo, "CSW.aEC", mPackageAbbreviation);
-
-            if (ConnectionServiceWrapper.this.mIsRemoteConnectionService) return;
-
             UserHandle userHandle = Binder.getCallingUserHandle();
             // Check that the Calling Package matches PhoneAccountHandle's Component Package
             PhoneAccountHandle callingPhoneAccountHandle = connection.getPhoneAccount();
@@ -1358,7 +1353,6 @@
     private final CallsManager mCallsManager;
     private final AppOpsManager mAppOpsManager;
     private final Context mContext;
-    public boolean mIsRemoteConnectionService = false;
 
     private ConnectionServiceFocusManager.ConnectionServiceFocusListener mConnSvrFocusListener;
 
@@ -2548,13 +2542,13 @@
     private void logIncoming(String msg, Object... params) {
         // Keep these as debug; the incoming logging is traced on a package level through the
         // session logging.
-        Log.d(this, "CS -> TC[" + Log.getPackageAbbreviation(mComponentName) + "]:"
-                + " isRCS = " + this.mIsRemoteConnectionService + ": " + msg, params);
+        Log.d(this, "CS -> TC[" + Log.getPackageAbbreviation(mComponentName) + "]: "
+                + msg, params);
     }
 
     private void logOutgoing(String msg, Object... params) {
-        Log.d(this, "TC -> CS[" + Log.getPackageAbbreviation(mComponentName) + "]:"
-                + " isRCS = " + this.mIsRemoteConnectionService + ": " + msg, params);
+        Log.d(this, "TC -> CS[" + Log.getPackageAbbreviation(mComponentName) + "]: "
+                + msg, params);
     }
 
     private void queryRemoteConnectionServices(final UserHandle userHandle,
@@ -2581,7 +2575,6 @@
             ConnectionServiceWrapper service = mConnectionServiceRepository.getService(
                     handle.getComponentName(), handle.getUserHandle());
             if (service != null && service != this) {
-                service.mIsRemoteConnectionService = true;
                 simServices.add(service);
             } else {
                 // This is unexpected, normally PhoneAccounts with CAPABILITY_CALL_PROVIDER are not
diff --git a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
index 6cf6ae4..8124f60 100644
--- a/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
+++ b/src/com/android/server/telecom/bluetooth/BluetoothRouteManager.java
@@ -45,6 +45,7 @@
 import java.util.HashSet;
 import java.util.LinkedHashSet;
 import java.util.Map;
+import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
@@ -134,7 +135,8 @@
         @Override
         public void enter() {
             BluetoothDevice erroneouslyConnectedDevice = getBluetoothAudioConnectedDevice();
-            if (erroneouslyConnectedDevice != null) {
+            if (erroneouslyConnectedDevice != null &&
+                !erroneouslyConnectedDevice.equals(mHearingAidActiveDeviceCache)) {
                 Log.w(LOG_TAG, "Entering AudioOff state but device %s appears to be connected. " +
                         "Switching to audio-on state for that device.", erroneouslyConnectedDevice);
                 // change this to just transition to the new audio on state
@@ -252,6 +254,27 @@
             SomeArgs args = (SomeArgs) msg.obj;
             String address = (String) args.arg2;
             boolean switchingBtDevices = !Objects.equals(mDeviceAddress, address);
+
+            if (switchingBtDevices == true) { // check if it is an hearing aid pair
+                BluetoothAdapter bluetoothAdapter = mDeviceManager.getBluetoothAdapter();
+                if (bluetoothAdapter != null) {
+                    List<BluetoothDevice> activeHearingAids =
+                      bluetoothAdapter.getActiveDevices(BluetoothProfile.HEARING_AID);
+                    for (BluetoothDevice hearingAid : activeHearingAids) {
+                        if (hearingAid != null) {
+                            String hearingAidAddress = hearingAid.getAddress();
+                            if (hearingAidAddress != null) {
+                                if (hearingAidAddress.equals(address) ||
+                                    hearingAidAddress.equals(mDeviceAddress)) {
+                                    switchingBtDevices = false;
+                                    break;
+                                }
+                            }
+                        }
+                    }
+
+                }
+            }
             try {
                 switch (msg.what) {
                     case NEW_DEVICE_CONNECTED:
@@ -393,8 +416,13 @@
                         String actualAddress = connectBtAudio(address,
                             true /* switchingBtDevices*/);
                         if (actualAddress != null) {
-                            transitionTo(getConnectingStateForAddress(address,
-                                    "AudioConnected/CONNECT_BT"));
+                            if (mFeatureFlags.useActualAddressToEnterConnectingState()) {
+                                transitionTo(getConnectingStateForAddress(actualAddress,
+                                        "AudioConnected/CONNECT_BT"));
+                            } else {
+                                transitionTo(getConnectingStateForAddress(address,
+                                        "AudioConnected/CONNECT_BT"));
+                            }
                         } else {
                             Log.w(LOG_TAG, "Tried to connect to %s but failed" +
                                     " to connect to any BT device.", (String) args.arg2);
@@ -863,12 +891,18 @@
                 : mDeviceManager.isHearingAidSetAsCommunicationDevice();
         if (bluetoothHearingAid != null) {
             if (isHearingAidSetForCommunication) {
-                for (BluetoothDevice device : bluetoothAdapter.getActiveDevices(
-                        BluetoothProfile.HEARING_AID)) {
-                    if (device != null) {
-                        hearingAidActiveDevice = device;
-                        activeDevices++;
-                        break;
+                List<BluetoothDevice> hearingAidsActiveDevices = bluetoothAdapter.getActiveDevices(
+                        BluetoothProfile.HEARING_AID);
+                if (hearingAidsActiveDevices.contains(mHearingAidActiveDeviceCache)) {
+                    hearingAidActiveDevice = mHearingAidActiveDeviceCache;
+                    activeDevices++;
+                } else {
+                    for (BluetoothDevice device : hearingAidsActiveDevices) {
+                        if (device != null) {
+                            hearingAidActiveDevice = device;
+                            activeDevices++;
+                            break;
+                        }
                     }
                 }
             }
diff --git a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
index 3ed96a0..e1ef08a 100644
--- a/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/BluetoothRouteManagerTest.java
@@ -20,6 +20,7 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -37,6 +38,8 @@
 import android.telecom.Log;
 import android.test.suitebuilder.annotation.SmallTest;
 
+import android.media.AudioDeviceInfo;
+
 import com.android.internal.os.SomeArgs;
 import com.android.server.telecom.CallAudioCommunicationDeviceTracker;
 import com.android.server.telecom.TelecomSystem;
@@ -62,7 +65,14 @@
     static final BluetoothDevice DEVICE1 = makeBluetoothDevice("00:00:00:00:00:01");
     static final BluetoothDevice DEVICE2 = makeBluetoothDevice("00:00:00:00:00:02");
     static final BluetoothDevice DEVICE3 = makeBluetoothDevice("00:00:00:00:00:03");
-    static final BluetoothDevice HEARING_AID_DEVICE = makeBluetoothDevice("00:00:00:00:00:04");
+    static final BluetoothDevice HEARING_AID_DEVICE_LEFT = makeBluetoothDevice("CA:FE:DE:CA:00:01");
+    static final BluetoothDevice HEARING_AID_DEVICE_RIGHT =
+      makeBluetoothDevice("CA:FE:DE:CA:00:02");
+    // See HearingAidService#getActiveDevices
+    // Note: It is really important that the left HA is the first one. The left HA is always
+    // in the first index (0) and the right one in the second index (1).
+    static final BluetoothDevice[] HEARING_AIDS =
+      new BluetoothDevice[]{HEARING_AID_DEVICE_LEFT, HEARING_AID_DEVICE_RIGHT};
 
     @Mock private BluetoothAdapter mBluetoothAdapter;
     @Mock private BluetoothDeviceManager mDeviceManager;
@@ -87,6 +97,59 @@
 
     @SmallTest
     @Test
+    public void testConnectLeftHearingAidWhenLeftIsActive() {
+        BluetoothRouteManager sm = setupStateMachine(
+                BluetoothRouteManager.AUDIO_OFF_STATE_NAME, HEARING_AID_DEVICE_LEFT);
+        sm.onActiveDeviceChanged(HEARING_AID_DEVICE_LEFT,
+            BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID);
+        when(mDeviceManager.connectAudio(anyString(), anyBoolean())).thenReturn(true);
+        when(mDeviceManager.isHearingAidSetAsCommunicationDevice()).thenReturn(true);
+
+        setupConnectedDevices(null, HEARING_AIDS, null, null, HEARING_AIDS, null);
+        when(mBluetoothHeadset.getAudioState(nullable(BluetoothDevice.class)))
+          .thenReturn(BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+
+        executeRoutingAction(sm,
+            BluetoothRouteManager.NEW_DEVICE_CONNECTED, HEARING_AID_DEVICE_LEFT.getAddress());
+
+        executeRoutingAction(sm,
+            BluetoothRouteManager.CONNECT_BT, HEARING_AID_DEVICE_LEFT.getAddress());
+
+        assertEquals(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX
+            + ":" + HEARING_AID_DEVICE_LEFT.getAddress(), sm.getCurrentState().getName());
+
+        sm.quitNow();
+    }
+
+    @SmallTest
+    @Test
+    public void testConnectRightHearingAidWhenLeftIsActive() {
+        BluetoothRouteManager sm = setupStateMachine(
+                BluetoothRouteManager.AUDIO_OFF_STATE_NAME, HEARING_AID_DEVICE_RIGHT);
+        sm.onActiveDeviceChanged(HEARING_AID_DEVICE_LEFT,
+            BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID);
+        when(mDeviceManager.connectAudio(anyString(), anyBoolean())).thenReturn(true);
+        when(mDeviceManager.isHearingAidSetAsCommunicationDevice()).thenReturn(true);
+
+
+        setupConnectedDevices(null, HEARING_AIDS, null, null, HEARING_AIDS, null);
+        when(mBluetoothHeadset.getAudioState(nullable(BluetoothDevice.class)))
+          .thenReturn(BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+
+        executeRoutingAction(sm,
+            BluetoothRouteManager.NEW_DEVICE_CONNECTED, HEARING_AID_DEVICE_LEFT.getAddress());
+
+        executeRoutingAction(sm,
+            BluetoothRouteManager.CONNECT_BT, HEARING_AID_DEVICE_LEFT.getAddress());
+
+        assertEquals(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX
+            + ":" + HEARING_AID_DEVICE_LEFT.getAddress(), sm.getCurrentState().getName());
+
+        sm.quitNow();
+    }
+
+    @SmallTest
+    @Test
     public void testConnectBtRetryWhileNotConnected() {
         BluetoothRouteManager sm = setupStateMachine(
                 BluetoothRouteManager.AUDIO_OFF_STATE_NAME, null);
@@ -113,15 +176,15 @@
         BluetoothRouteManager sm = setupStateMachine(
                 BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX, DEVICE1);
         setupConnectedDevices(new BluetoothDevice[]{DEVICE1},
-                new BluetoothDevice[]{HEARING_AID_DEVICE}, new BluetoothDevice[]{DEVICE2},
-                DEVICE1, HEARING_AID_DEVICE, DEVICE2);
+                HEARING_AIDS, new BluetoothDevice[]{DEVICE2},
+                DEVICE1,  HEARING_AIDS, DEVICE2);
         sm.onActiveDeviceChanged(DEVICE1, BluetoothDeviceManager.DEVICE_TYPE_HEADSET);
         sm.onActiveDeviceChanged(DEVICE2, BluetoothDeviceManager.DEVICE_TYPE_LE_AUDIO);
-        sm.onActiveDeviceChanged(HEARING_AID_DEVICE,
+        sm.onActiveDeviceChanged(HEARING_AID_DEVICE_LEFT,
                 BluetoothDeviceManager.DEVICE_TYPE_HEARING_AID);
         executeRoutingAction(sm, BluetoothRouteManager.BT_AUDIO_LOST, DEVICE1.getAddress());
 
-        verifyConnectionAttempt(HEARING_AID_DEVICE, 0);
+        verifyConnectionAttempt(HEARING_AID_DEVICE_LEFT, 0);
         verifyConnectionAttempt(DEVICE1, 0);
         verifyConnectionAttempt(DEVICE2, 0);
         assertEquals(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX
@@ -176,15 +239,42 @@
     @Test
     public void testSkipInactiveBtDeviceWhenEvaluateActualState() {
         BluetoothRouteManager sm = setupStateMachine(
-                BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX, HEARING_AID_DEVICE);
-        setupConnectedDevices(null, new BluetoothDevice[]{HEARING_AID_DEVICE},
-                null, null, HEARING_AID_DEVICE, null);
+                BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX, HEARING_AID_DEVICE_LEFT);
+        setupConnectedDevices(null, HEARING_AIDS,
+                null, null, HEARING_AIDS, null);
         executeRoutingAction(sm, BluetoothRouteManager.BT_AUDIO_LOST,
-                HEARING_AID_DEVICE.getAddress());
+                HEARING_AID_DEVICE_LEFT.getAddress());
         assertEquals(BluetoothRouteManager.AUDIO_OFF_STATE_NAME, sm.getCurrentState().getName());
         sm.quitNow();
     }
 
+    @SmallTest
+    @Test
+    public void testConnectBtWithoutAddress() {
+        when(mFeatureFlags.useActualAddressToEnterConnectingState()).thenReturn(true);
+        BluetoothRouteManager sm = setupStateMachine(
+                BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX, DEVICE1);
+        setupConnectedDevices(new BluetoothDevice[]{DEVICE1, DEVICE2}, null, null, null, null,
+                null);
+        when(mTimeoutsAdapter.getRetryBluetoothConnectAudioBackoffMillis(
+                nullable(ContentResolver.class))).thenReturn(0L);
+        when(mBluetoothHeadset.connectAudio()).thenReturn(BluetoothStatusCodes.ERROR_UNKNOWN);
+        executeRoutingAction(sm, BluetoothRouteManager.CONNECT_BT, null);
+        // Wait 3 times: the first connection attempt is accounted for in executeRoutingAction,
+        // so wait twice for the retry attempt, again to make sure there are only three attempts,
+        // and once more for good luck.
+        waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+        waitForHandlerAction(sm.getHandler(), TEST_TIMEOUT);
+        verifyConnectionAttempt(DEVICE1, 1);
+        assertEquals(BluetoothRouteManager.AUDIO_CONNECTED_STATE_NAME_PREFIX
+                        + ":" + DEVICE1.getAddress(),
+                sm.getCurrentState().getName());
+        sm.getHandler().removeMessages(BluetoothRouteManager.CONNECTION_TIMEOUT);
+        sm.quitNow();
+    }
+
     private BluetoothRouteManager setupStateMachine(String initialState,
             BluetoothDevice initialDevice) {
         resetMocks();
@@ -200,10 +290,11 @@
 
     private void setupConnectedDevices(BluetoothDevice[] hfpDevices,
             BluetoothDevice[] hearingAidDevices, BluetoothDevice[] leAudioDevices,
-            BluetoothDevice hfpActiveDevice, BluetoothDevice hearingAidActiveDevice,
+            BluetoothDevice hfpActiveDevice, BluetoothDevice[] hearingAidActiveDevices,
             BluetoothDevice leAudioDevice) {
         if (hfpDevices == null) hfpDevices = new BluetoothDevice[]{};
         if (hearingAidDevices == null) hearingAidDevices = new BluetoothDevice[]{};
+        if (hearingAidActiveDevices == null) hearingAidActiveDevices = new BluetoothDevice[]{};
         if (leAudioDevice == null) leAudioDevices = new BluetoothDevice[]{};
 
         when(mDeviceManager.getNumConnectedDevices()).thenReturn(
@@ -222,7 +313,7 @@
         when(mBluetoothHearingAid.getConnectedDevices())
                 .thenReturn(Arrays.asList(hearingAidDevices));
         when(mBluetoothAdapter.getActiveDevices(eq(BluetoothProfile.HEARING_AID)))
-                .thenReturn(Arrays.asList(hearingAidActiveDevice, null));
+                .thenReturn(Arrays.asList(hearingAidActiveDevices));
         when(mBluetoothAdapter.getActiveDevices(eq(BluetoothProfile.LE_AUDIO)))
                 .thenReturn(Arrays.asList(leAudioDevice, null));
     }