Activate the last connected BT device

When BT1 connected -> BT2 connected -> BT2 disconnected,
then the sound came from phone speaker before this CL.
With the CL, now the sound comes from BT1.

Bug: 243053736
Tag: #refactor
Test: atest BluetoothInstrumentationTests:ActiveDeviceManagerTest
Change-Id: I1d019bfd117f7e9f13618dfa989c331b33be3f42
(cherry picked from commit 225fef2717d6daa7e027447939d22c17da2339ea)
Merged-In: I1d019bfd117f7e9f13618dfa989c331b33be3f42
diff --git a/android/app/src/com/android/bluetooth/a2dp/A2dpService.java b/android/app/src/com/android/bluetooth/a2dp/A2dpService.java
index 313f64c..5c160ac 100644
--- a/android/app/src/com/android/bluetooth/a2dp/A2dpService.java
+++ b/android/app/src/com/android/bluetooth/a2dp/A2dpService.java
@@ -1292,7 +1292,7 @@
     /**
      * Retrieves the most recently connected device in the A2DP connected devices list.
      */
-    private BluetoothDevice getFallbackDevice() {
+    public BluetoothDevice getFallbackDevice() {
         DatabaseManager dbManager = mAdapterService.getDatabase();
         return dbManager != null ? dbManager
             .getMostRecentlyConnectedDevicesInList(getConnectedDevices())
diff --git a/android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java b/android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
index 1f63a88..ec3f4f4 100644
--- a/android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
+++ b/android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java
@@ -40,6 +40,7 @@
 import android.util.Log;
 
 import com.android.bluetooth.a2dp.A2dpService;
+import com.android.bluetooth.btservice.storage.DatabaseManager;
 import com.android.bluetooth.hearingaid.HearingAidService;
 import com.android.bluetooth.hfp.HeadsetService;
 import com.android.bluetooth.le_audio.LeAudioService;
@@ -51,8 +52,10 @@
 
 /**
  * The active device manager is responsible for keeping track of the
- * connected A2DP/HFP/AVRCP/HearingAid devices and select which device is
+ * connected A2DP/HFP/AVRCP/HearingAid/LE audio devices and select which device is
  * active (for each profile).
+ * The active device manager selects a fallback device when the currently active device
+ * is disconnected, and it selects BT devices that are lastly activated one.
  *
  * Current policy (subject to change):
  * 1) If the maximum number of connected devices is one, the manager doesn't
@@ -66,24 +69,24 @@
  *    - BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED for A2DP
  *    - BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED for HFP
  *    - BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED for HearingAid
+ *    - BluetoothLeAudio.ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED for LE audio
  *    If such broadcast is received (e.g., triggered indirectly by user
- *    action on the UI), the device in the received broacast is marked
+ *    action on the UI), the device in the received broadcast is marked
  *    as the current active device for that profile.
- * 5) If there is a HearingAid active device, then A2DP and HFP active devices
- *    must be set to null (i.e., A2DP and HFP cannot have active devices).
- *    The reason is because A2DP or HFP cannot be used together with HearingAid.
+ * 5) If there is a HearingAid active device, then A2DP, HFP and LE audio active devices
+ *    must be set to null (i.e., A2DP, HFP and LE audio cannot have active devices).
+ *    The reason is that A2DP, HFP or LE audio cannot be used together with HearingAid.
  * 6) If there are no connected devices (e.g., during startup, or after all
  *    devices have been disconnected, the active device per profile
- *    (A2DP/HFP/HearingAid) is selected as follows:
+ *    (A2DP/HFP/HearingAid/LE audio) is selected as follows:
  * 6.1) The last connected HearingAid device is selected as active.
- *      If there is an active A2DP or HFP device, those must be set to null.
- * 6.2) The last connected A2DP or HFP device is selected as active.
+ *      If there is an active A2DP, HFP or LE audio device, those must be set to null.
+ * 6.2) The last connected A2DP, HFP or LE audio device is selected as active.
  *      However, if there is an active HearingAid device, then the
- *      A2DP or HFP active device is not set (must remain null).
+ *      A2DP, HFP, or LE audio active device is not set (must remain null).
  * 7) If the currently active device (per profile) is disconnected, the
  *    Active Device Manager just marks that the profile has no active device,
- *    but does not attempt to select a new one. Currently, the expectation is
- *    that the user will explicitly select the new active device.
+ *    and the lastly activated BT device that is still connected would be selected.
  * 8) If there is already an active device, and the corresponding
  *    ACTION_ACTIVE_DEVICE_CHANGED broadcast is received, the device
  *    contained in the broadcast is marked as active. However, if
@@ -91,7 +94,7 @@
  *    as having no active device.
  * 9) If a wired audio device is connected, the audio output is switched
  *    by the Audio Framework itself to that device. We detect this here,
- *    and the active device for each profile (A2DP/HFP/HearingAid) is set
+ *    and the active device for each profile (A2DP/HFP/HearingAid/LE audio) is set
  *    to null to reflect the output device state change. However, if the
  *    wired audio device is disconnected, we don't do anything explicit
  *    and apply the default behavior instead:
@@ -252,9 +255,11 @@
                                     + "device " + device + " disconnected");
                         }
                         mA2dpConnectedDevices.remove(device);
-                        if (mA2dpConnectedDevices.isEmpty()
-                                && Objects.equals(mA2dpActiveDevice, device)) {
-                            setA2dpActiveDevice(null);
+                        if (Objects.equals(mA2dpActiveDevice, device)) {
+                            if (mA2dpConnectedDevices.isEmpty()) {
+                                setA2dpActiveDevice(null);
+                            }
+                            setFallbackDeviceActive();
                         }
                     }
                 }
@@ -314,9 +319,11 @@
                                     + "device " + device + " disconnected");
                         }
                         mHfpConnectedDevices.remove(device);
-                        if (mHfpConnectedDevices.isEmpty()
-                                && Objects.equals(mHfpActiveDevice, device)) {
-                            setHfpActiveDevice(null);
+                        if (Objects.equals(mHfpActiveDevice, device)) {
+                            if (mHfpConnectedDevices.isEmpty()) {
+                                setHfpActiveDevice(null);
+                            }
+                            setFallbackDeviceActive();
                         }
                     }
                 }
@@ -392,9 +399,11 @@
                                     + "_CHANGED): device " + device + " disconnected");
                         }
                         mLeAudioConnectedDevices.remove(device);
-                        if (mLeAudioConnectedDevices.isEmpty()
-                                && Objects.equals(mLeAudioActiveDevice, device)) {
-                            setLeAudioActiveDevice(null);
+                        if (Objects.equals(mLeAudioActiveDevice, device)) {
+                            if (mLeAudioConnectedDevices.isEmpty()) {
+                                setLeAudioActiveDevice(null);
+                            }
+                            setFallbackDeviceActive();
                         }
                     }
                 }
@@ -658,6 +667,87 @@
         }
     }
 
+    private void setFallbackDeviceActive() {
+        if (DBG) {
+            Log.d(TAG, "setFallbackDeviceActive");
+        }
+        DatabaseManager dbManager = mAdapterService.getDatabase();
+        if (dbManager == null) {
+            return;
+        }
+
+        A2dpService a2dpService = mFactory.getA2dpService();
+        BluetoothDevice a2dpFallbackDevice = null;
+        if (a2dpService != null) {
+            a2dpFallbackDevice = a2dpService.getFallbackDevice();
+        }
+
+        HeadsetService headsetService = mFactory.getHeadsetService();
+        BluetoothDevice headsetFallbackDevice = null;
+        if (headsetService != null) {
+            headsetFallbackDevice = headsetService.getFallbackDevice();
+        }
+
+        List<BluetoothDevice> connectedDevices = new LinkedList<>();
+        connectedDevices.addAll(mLeAudioConnectedDevices);
+        switch (mAudioManager.getMode()) {
+            case AudioManager.MODE_NORMAL:
+                if (a2dpFallbackDevice != null) {
+                    connectedDevices.add(a2dpFallbackDevice);
+                }
+                break;
+            case AudioManager.MODE_RINGTONE:
+                if (headsetFallbackDevice != null && headsetService.isInbandRingingEnabled()) {
+                    connectedDevices.add(headsetFallbackDevice);
+                }
+                break;
+            default:
+                if (headsetFallbackDevice != null) {
+                    connectedDevices.add(headsetFallbackDevice);
+                }
+        }
+        BluetoothDevice device = dbManager.getMostRecentlyConnectedDevicesInList(connectedDevices);
+        if (device != null) {
+            if (mAudioManager.getMode() == AudioManager.MODE_NORMAL) {
+                if (Objects.equals(a2dpFallbackDevice, device)) {
+                    if (DBG) {
+                        Log.d(TAG, "set A2DP device active: " + device);
+                    }
+                    setA2dpActiveDevice(device);
+                    if (headsetFallbackDevice != null) {
+                        setHfpActiveDevice(device);
+                        setLeAudioActiveDevice(null);
+                    }
+                } else {
+                    if (DBG) {
+                        Log.d(TAG, "set LE audio device active: " + device);
+                    }
+                    setLeAudioActiveDevice(device);
+                    setA2dpActiveDevice(null);
+                    setHfpActiveDevice(null);
+                }
+            } else {
+                if (Objects.equals(headsetFallbackDevice, device)) {
+                    if (DBG) {
+                        Log.d(TAG, "set HFP device active: " + device);
+                    }
+                    setHfpActiveDevice(device);
+                    if (a2dpFallbackDevice != null) {
+                        setA2dpActiveDevice(a2dpFallbackDevice);
+                        setLeAudioActiveDevice(null);
+                    }
+                } else {
+                    if (DBG) {
+                        Log.d(TAG, "set LE audio device active: " + device);
+                    }
+                    setLeAudioActiveDevice(device);
+                    setA2dpActiveDevice(null);
+                    setHfpActiveDevice(null);
+                }
+            }
+        }
+    }
+
     private void resetState() {
         mA2dpConnectedDevices.clear();
         mA2dpActiveDevice = null;
diff --git a/android/app/src/com/android/bluetooth/hfp/HeadsetService.java b/android/app/src/com/android/bluetooth/hfp/HeadsetService.java
index 275ae63..a951616 100644
--- a/android/app/src/com/android/bluetooth/hfp/HeadsetService.java
+++ b/android/app/src/com/android/bluetooth/hfp/HeadsetService.java
@@ -1927,7 +1927,12 @@
         return true;
     }
 
-    boolean isInbandRingingEnabled() {
+    /**
+     * Checks if headset devices are able to get inband ringing.
+     *
+     * @return True if inband ringing is enabled.
+     */
+    public boolean isInbandRingingEnabled() {
         boolean isInbandRingingSupported = getResources().getBoolean(
                 com.android.bluetooth.R.bool.config_bluetooth_hfp_inband_ringing_support);
         return isInbandRingingSupported && !SystemProperties.getBoolean(
@@ -2176,7 +2181,7 @@
     /**
      * Retrieves the most recently connected device in the A2DP connected devices list.
      */
-    private BluetoothDevice getFallbackDevice() {
+    public BluetoothDevice getFallbackDevice() {
         DatabaseManager dbManager = mAdapterService.getDatabase();
         return dbManager != null ? dbManager
             .getMostRecentlyConnectedDevicesInList(getConnectedDevices())
diff --git a/android/app/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java b/android/app/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java
index 28e68cf..f182375 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/btservice/ActiveDeviceManagerTest.java
@@ -50,6 +50,8 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.List;
+
 @MediumTest
 @RunWith(AndroidJUnit4.class)
 public class ActiveDeviceManagerTest {
@@ -474,6 +476,56 @@
     }
 
     /**
+     * An LE Audio connected. An A2DP connected. The A2DP disconnected.
+     * Then the LE Audio should be the active one.
+     */
+    @Test
+    public void leAudioAndA2dpConnectedThenA2dpDisconnected_fallbackToLeAudio() {
+        leAudioConnected(mLeAudioDevice);
+        verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice);
+
+        a2dpConnected(mA2dpDevice);
+        verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice);
+
+        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);
+        when(mDatabaseManager.getMostRecentlyConnectedDevicesInList(any())).thenAnswer(
+                invocation -> {
+                    List<BluetoothDevice> devices = invocation.getArgument(0);
+                    return (devices != null && devices.contains(mLeAudioDevice))
+                            ? mLeAudioDevice : null;
+                }
+        );
+        a2dpDisconnected(mA2dpDevice);
+        verify(mA2dpService, timeout(TIMEOUT_MS).atLeast(1)).setActiveDevice(isNull());
+        verify(mLeAudioService, timeout(TIMEOUT_MS).times(2)).setActiveDevice(mLeAudioDevice);
+    }
+
+    /**
+     * An A2DP connected. An LE Audio connected. The LE Audio disconnected.
+     * Then the A2DP should be the active one.
+     */
+    @Test
+    public void a2dpAndLeAudioConnectedThenLeAudioDisconnected_fallbackToA2dp() {
+        a2dpConnected(mA2dpDevice);
+        verify(mA2dpService, timeout(TIMEOUT_MS)).setActiveDevice(mA2dpDevice);
+
+        leAudioConnected(mLeAudioDevice);
+        verify(mLeAudioService, timeout(TIMEOUT_MS)).setActiveDevice(mLeAudioDevice);
+
+        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);
+        when(mA2dpService.getFallbackDevice()).thenReturn(mA2dpDevice);
+        when(mDatabaseManager.getMostRecentlyConnectedDevicesInList(any())).thenAnswer(
+                invocation -> {
+                    List<BluetoothDevice> devices = invocation.getArgument(0);
+                    return (devices != null && devices.contains(mA2dpDevice)) ? mA2dpDevice : null;
+                }
+        );
+        leAudioDisconnected(mLeAudioDevice);
+        verify(mLeAudioService, timeout(TIMEOUT_MS).atLeast(1)).setActiveDevice(isNull());
+        verify(mA2dpService, timeout(TIMEOUT_MS).times(2)).setActiveDevice(mA2dpDevice);
+    }
+
+    /**
      * One LE Hearing Aid is connected.
      */
     @Test