Find active device for add hearing aids device into audio switcher.

- Find active device accroding to different stream type and
output device.

- update isStreamFromOutputDevice() to identify general case like
DEVICE_OUT_BLUETOOTH_A2DP is subset of DEVICE_OUT_ALL_A2DP.

- add test case for these methods.

Bug: 78142719
Test: make RunSettingsRoboTests ROBOTEST_FILTER="AudioOutputSwitchPreferenceControllerTest" -j28
Change-Id: I381135c120dbf051679bff7626d47e41f8d589da
diff --git a/src/com/android/settings/sound/AudioSwitchPreferenceController.java b/src/com/android/settings/sound/AudioSwitchPreferenceController.java
index 43cd29f..ea7e8fe 100644
--- a/src/com/android/settings/sound/AudioSwitchPreferenceController.java
+++ b/src/com/android/settings/sound/AudioSwitchPreferenceController.java
@@ -203,7 +203,7 @@
     }
 
     protected boolean isStreamFromOutputDevice(int streamType, int device) {
-        return mAudioManager.getDevicesForStream(streamType) == device;
+        return (device & mAudioManager.getDevicesForStream(streamType)) != 0;
     }
 
     protected boolean isOngoingCallStatus() {
@@ -272,6 +272,40 @@
         return connectedDevices;
     }
 
+    /**
+     * According to different stream and output device, find the active device from
+     * the corresponding profile. Hearing aid device could stream both STREAM_MUSIC
+     * and STREAM_VOICE_CALL.
+     *
+     * @param streamType the type of audio streams.
+     * @return the active device. Return null if the active device is current device
+     * or streamType is not STREAM_MUSIC or STREAM_VOICE_CALL.
+     */
+    protected BluetoothDevice findActiveDevice(int streamType) {
+        if (streamType != STREAM_MUSIC && streamType != STREAM_VOICE_CALL) {
+            return null;
+        }
+        if (isStreamFromOutputDevice(STREAM_MUSIC, DEVICE_OUT_ALL_A2DP)) {
+            return mProfileManager.getA2dpProfile().getActiveDevice();
+        } else if (isStreamFromOutputDevice(STREAM_VOICE_CALL, DEVICE_OUT_ALL_SCO)) {
+            return mProfileManager.getHeadsetProfile().getActiveDevice();
+        } else if (isStreamFromOutputDevice(streamType, DEVICE_OUT_HEARING_AID)) {
+            // The first element is the left active device; the second element is
+            // the right active device. And they will have same hiSyncId. If either
+            // or both side is not active, it will be null on that position.
+            List<BluetoothDevice> activeDevices =
+                    mProfileManager.getHearingAidProfile().getActiveDevices();
+            for (BluetoothDevice btDevice : activeDevices) {
+                if (btDevice != null && mConnectedDevices.contains(btDevice)) {
+                    // also need to check mConnectedDevices, because one of
+                    // the device(same hiSyncId) might not be shown in the UI.
+                    return btDevice;
+                }
+            }
+        }
+        return null;
+    }
+
     int getDefaultDeviceIndex() {
         // Default device is after all connected devices.
         return ArrayUtils.size(mConnectedDevices);
diff --git a/tests/robotests/src/com/android/settings/sound/AudioOutputSwitchPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/sound/AudioOutputSwitchPreferenceControllerTest.java
index df65297..61b9180 100644
--- a/tests/robotests/src/com/android/settings/sound/AudioOutputSwitchPreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/sound/AudioOutputSwitchPreferenceControllerTest.java
@@ -17,6 +17,15 @@
 package com.android.settings.sound;
 
 
+import static android.media.AudioManager.DEVICE_OUT_BLUETOOTH_SCO;
+import static android.media.AudioManager.STREAM_RING;
+import static android.media.AudioManager.STREAM_VOICE_CALL;
+import static android.media.AudioSystem.DEVICE_OUT_ALL_SCO;
+import static android.media.AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP;
+import static android.media.AudioSystem.DEVICE_OUT_BLUETOOTH_SCO_HEADSET;
+import static android.media.AudioSystem.DEVICE_OUT_HEARING_AID;
+import static android.media.AudioSystem.STREAM_MUSIC;
+
 import static com.android.settings.core.BasePreferenceController.AVAILABLE;
 import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE;
 
@@ -254,6 +263,147 @@
     }
 
     /**
+     * Audio stream output to bluetooth sco headset which is the subset of all sco device.
+     * isStreamFromOutputDevice should return true.
+     */
+    @Test
+    public void isStreamFromOutputDevice_outputDeviceIsBtScoHeadset_shouldReturnTrue() {
+        mShadowAudioManager.setStream(DEVICE_OUT_BLUETOOTH_SCO_HEADSET);
+
+        assertThat(mController.isStreamFromOutputDevice(STREAM_MUSIC, DEVICE_OUT_ALL_SCO)).isTrue();
+    }
+
+    /**
+     * Audio stream is not STREAM_MUSIC or STREAM_VOICE_CALL.
+     * findActiveDevice should return null.
+     */
+    @Test
+    public void findActiveDevice_streamIsRing_shouldReturnNull() {
+        assertThat(mController.findActiveDevice(STREAM_RING)).isNull();
+    }
+
+    /**
+     * Audio stream is STREAM_MUSIC and output device is A2dp bluetooth device.
+     * findActiveDevice should return A2dp active device.
+     */
+    @Test
+    public void findActiveDevice_streamMusicToA2dpDevice_shouldReturnActiveA2dpDevice() {
+        mShadowAudioManager.setStream(DEVICE_OUT_BLUETOOTH_A2DP);
+        mHearingAidActiveDevices.clear();
+        mHearingAidActiveDevices.add(mBluetoothHapDevice);
+        when(mHeadsetProfile.getActiveDevice()).thenReturn(mBluetoothHapDevice);
+        when(mA2dpProfile.getActiveDevice()).thenReturn(mBluetoothDevice);
+        when(mHearingAidProfile.getActiveDevices()).thenReturn(mHearingAidActiveDevices);
+
+        assertThat(mController.findActiveDevice(STREAM_MUSIC)).isEqualTo(mBluetoothDevice);
+    }
+
+    /**
+     * Audio stream is STREAM_VOICE_CALL and output device is Hands free profile bluetooth device.
+     * findActiveDevice should return Hands free profile active device.
+     */
+    @Test
+    public void findActiveDevice_streamVoiceCallToHfpDevice_shouldReturnActiveHfpDevice() {
+        mShadowAudioManager.setStream(DEVICE_OUT_BLUETOOTH_SCO);
+        mHearingAidActiveDevices.clear();
+        mHearingAidActiveDevices.add(mBluetoothHapDevice);
+        when(mHeadsetProfile.getActiveDevice()).thenReturn(mBluetoothDevice);
+        when(mA2dpProfile.getActiveDevice()).thenReturn(mBluetoothHapDevice);
+        when(mHearingAidProfile.getActiveDevices()).thenReturn(mHearingAidActiveDevices);
+
+        assertThat(mController.findActiveDevice(STREAM_VOICE_CALL)).isEqualTo(mBluetoothDevice);
+    }
+
+    /**
+     * Audio stream is STREAM_MUSIC or STREAM_VOICE_CALL and output device is hearing aid profile
+     * bluetooth device. And left side of HAP device is active.
+     * findActiveDevice should return hearing aid device active device.
+     */
+    @Test
+    public void findActiveDevice_streamToHapDeviceLeftActiveDevice_shouldReturnActiveHapDevice() {
+        mShadowAudioManager.setStream(DEVICE_OUT_HEARING_AID);
+        mConnectedDevices.clear();
+        mConnectedDevices.add(mBluetoothDevice);
+        mConnectedDevices.add(mBluetoothHapDevice);
+        mHearingAidActiveDevices.clear();
+        mHearingAidActiveDevices.add(mBluetoothHapDevice);
+        mController.mConnectedDevices = mConnectedDevices;
+        when(mHeadsetProfile.getActiveDevice()).thenReturn(mBluetoothDevice);
+        when(mA2dpProfile.getActiveDevice()).thenReturn(mBluetoothDevice);
+        when(mHearingAidProfile.getActiveDevices()).thenReturn(mHearingAidActiveDevices);
+
+        assertThat(mController.findActiveDevice(STREAM_MUSIC)).isEqualTo(mBluetoothHapDevice);
+        assertThat(mController.findActiveDevice(STREAM_VOICE_CALL)).isEqualTo(mBluetoothHapDevice);
+    }
+
+    /**
+     * Audio stream is STREAM_MUSIC or STREAM_VOICE_CALL and output device is hearing aid profile
+     * bluetooth device. And right side of HAP device is active.
+     * findActiveDevice should return hearing aid device active device.
+     */
+    @Test
+    public void findActiveDevice_streamToHapDeviceRightActiveDevice_shouldReturnActiveHapDevice() {
+        mShadowAudioManager.setStream(DEVICE_OUT_HEARING_AID);
+        mConnectedDevices.clear();
+        mConnectedDevices.add(mBluetoothDevice);
+        mConnectedDevices.add(mBluetoothHapDevice);
+        mHearingAidActiveDevices.clear();
+        mHearingAidActiveDevices.add(null);
+        mHearingAidActiveDevices.add(mBluetoothHapDevice);
+        mController.mConnectedDevices = mConnectedDevices;
+        when(mHeadsetProfile.getActiveDevice()).thenReturn(mBluetoothDevice);
+        when(mA2dpProfile.getActiveDevice()).thenReturn(mBluetoothDevice);
+        when(mHearingAidProfile.getActiveDevices()).thenReturn(mHearingAidActiveDevices);
+
+        assertThat(mController.findActiveDevice(STREAM_MUSIC)).isEqualTo(mBluetoothHapDevice);
+        assertThat(mController.findActiveDevice(STREAM_VOICE_CALL)).isEqualTo(mBluetoothHapDevice);
+    }
+
+    /**
+     * Audio stream is STREAM_MUSIC or STREAM_VOICE_CALL and output device is hearing aid
+     * profile bluetooth device. And both are active device.
+     * findActiveDevice should return only return the active device in mConnectedDevices.
+     */
+    @Test
+    public void findActiveDevice_streamToHapDeviceTwoActiveDevice_shouldReturnActiveHapDevice() {
+        mShadowAudioManager.setStream(DEVICE_OUT_HEARING_AID);
+        mConnectedDevices.clear();
+        mConnectedDevices.add(mBluetoothDevice);
+        mConnectedDevices.add(mBluetoothHapDevice);
+        mHearingAidActiveDevices.clear();
+        mHearingAidActiveDevices.add(mSecondBluetoothHapDevice);
+        mHearingAidActiveDevices.add(mBluetoothHapDevice);
+        mController.mConnectedDevices = mConnectedDevices;
+        when(mHeadsetProfile.getActiveDevice()).thenReturn(mBluetoothDevice);
+        when(mA2dpProfile.getActiveDevice()).thenReturn(mBluetoothDevice);
+        when(mHearingAidProfile.getActiveDevices()).thenReturn(mHearingAidActiveDevices);
+
+        assertThat(mController.findActiveDevice(STREAM_MUSIC)).isEqualTo(mBluetoothHapDevice);
+        assertThat(mController.findActiveDevice(STREAM_VOICE_CALL)).isEqualTo(mBluetoothHapDevice);
+    }
+
+    /**
+     * Audio stream is STREAM_MUSIC or STREAM_VOICE_CALL and output device is hearing aid
+     * profile bluetooth device. And none of them are active.
+     * findActiveDevice should return null.
+     */
+    @Test
+    public void findActiveDevice_streamToOtherDevice_shouldReturnActiveHapDevice() {
+        mShadowAudioManager.setStream(DEVICE_OUT_HEARING_AID);
+        mConnectedDevices.clear();
+        mConnectedDevices.add(mBluetoothDevice);
+        mConnectedDevices.add(mBluetoothHapDevice);
+        mHearingAidActiveDevices.clear();
+        mController.mConnectedDevices = mConnectedDevices;
+        when(mHeadsetProfile.getActiveDevice()).thenReturn(mBluetoothDevice);
+        when(mA2dpProfile.getActiveDevice()).thenReturn(mBluetoothDevice);
+        when(mHearingAidProfile.getActiveDevices()).thenReturn(mHearingAidActiveDevices);
+
+        assertThat(mController.findActiveDevice(STREAM_MUSIC)).isNull();
+        assertThat(mController.findActiveDevice(STREAM_VOICE_CALL)).isNull();
+    }
+
+    /**
      * Two hearing aid devices with different HisyncId
      * getConnectedHearingAidDevices should add both device to list.
      */