Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7fcd2b8b authored by jasonwshsu's avatar jasonwshsu
Browse files

[HA Input] Add ability to alter input for calls when hearing device connected

Bug: 349255906
Test: atest HearingAidAudioRoutingHelperTest
Flag: com.android.settingslib.flags.hearing_devices_input_routing_control
Change-Id: I951dfdf0b85b78d1aadbb0bf86c2f556619e0e51
parent 48237df9
Loading
Loading
Loading
Loading
+9 −3
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.settingslib.bluetooth;
import android.media.AudioAttributes;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.media.MediaRecorder;

import androidx.annotation.IntDef;

@@ -61,15 +62,20 @@ public final class HearingAidAudioRoutingConstants {
    @IntDef({
            RoutingValue.AUTO,
            RoutingValue.HEARING_DEVICE,
            RoutingValue.DEVICE_SPEAKER,
            RoutingValue.BUILTIN_DEVICE,
    })

    public @interface RoutingValue {
        int AUTO = 0;
        int HEARING_DEVICE = 1;
        int DEVICE_SPEAKER = 2;
        int BUILTIN_DEVICE = 2;
    }

    public static final AudioDeviceAttributes DEVICE_SPEAKER_OUT = new AudioDeviceAttributes(
    public static final AudioDeviceAttributes BUILTIN_SPEAKER = new AudioDeviceAttributes(
            AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, "");
    public static final AudioDeviceAttributes BUILTIN_MIC = new AudioDeviceAttributes(
            AudioDeviceAttributes.ROLE_INPUT, AudioDeviceInfo.TYPE_BUILTIN_MIC, "");

    public static final int MICROPHONE_SOURCE_VOICE_COMMUNICATION =
            MediaRecorder.AudioSource.VOICE_COMMUNICATION;
}
+102 −11
Original line number Diff line number Diff line
@@ -16,26 +16,34 @@

package com.android.settingslib.bluetooth;

import static com.android.settingslib.bluetooth.HearingAidAudioRoutingConstants.BUILTIN_MIC;
import static com.android.settingslib.bluetooth.HearingAidAudioRoutingConstants.MICROPHONE_SOURCE_VOICE_COMMUNICATION;

import android.content.Context;
import android.media.AudioAttributes;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.audiopolicy.AudioProductStrategy;
import android.util.Log;

import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;

import com.android.settingslib.bluetooth.HearingAidAudioRoutingConstants.RoutingValue;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * A helper class to configure the routing strategy for hearing aids.
 * A helper class to configure the audio routing for hearing aids.
 */
public class HearingAidAudioRoutingHelper {

    private static final String TAG = "HearingAidAudioRoutingHelper";

    private final AudioManager mAudioManager;

    public HearingAidAudioRoutingHelper(Context context) {
@@ -73,26 +81,26 @@ public class HearingAidAudioRoutingHelper {
     * @param hearingDevice {@link AudioDeviceAttributes} of the device to be changed in audio
     *                      routing
     * @param routingValue one of value defined in
     *                     {@link HearingAidAudioRoutingConstants.RoutingValue}, denotes routing
     *                     {@link RoutingValue}, denotes routing
     *                     destination.
     * @return {code true} if the routing value successfully configure
     */
    public boolean setPreferredDeviceRoutingStrategies(
            List<AudioProductStrategy> supportedStrategies, AudioDeviceAttributes hearingDevice,
            @HearingAidAudioRoutingConstants.RoutingValue int routingValue) {
            @RoutingValue int routingValue) {
        boolean status;
        switch (routingValue) {
            case HearingAidAudioRoutingConstants.RoutingValue.AUTO:
            case RoutingValue.AUTO:
                status = removePreferredDeviceForStrategies(supportedStrategies);
                return status;
            case HearingAidAudioRoutingConstants.RoutingValue.HEARING_DEVICE:
            case RoutingValue.HEARING_DEVICE:
                status = removePreferredDeviceForStrategies(supportedStrategies);
                status &= setPreferredDeviceForStrategies(supportedStrategies, hearingDevice);
                return status;
            case HearingAidAudioRoutingConstants.RoutingValue.DEVICE_SPEAKER:
            case RoutingValue.BUILTIN_DEVICE:
                status = removePreferredDeviceForStrategies(supportedStrategies);
                status &= setPreferredDeviceForStrategies(supportedStrategies,
                        HearingAidAudioRoutingConstants.DEVICE_SPEAKER_OUT);
                        HearingAidAudioRoutingConstants.BUILTIN_SPEAKER);
                return status;
            default:
                throw new IllegalArgumentException("Unexpected routingValue: " + routingValue);
@@ -100,21 +108,76 @@ public class HearingAidAudioRoutingHelper {
    }

    /**
     * Gets the matched hearing device {@link AudioDeviceAttributes} for {@code device}.
     * Set the preferred input device for calls.
     *
     * <p>Will also try to match the {@link CachedBluetoothDevice#getSubDevice()} of {@code device}
     * <p>Note that hearing device needs to be valid input device to be found in AudioManager.
     * <p>Routing value can be:
     * <ul>
     *     <li> {@link RoutingValue#AUTO} - Allow the system to automatically select the appropriate
     *     audio routing for calls.</li>
     *     <li> {@link RoutingValue#HEARING_DEVICE} - Set input device to this hearing device.</li>
     *     <li> {@link RoutingValue#BUILTIN_DEVICE} - Set input device to builtin microphone. </li>
     * </ul>
     * @param routingValue The desired routing value for calls
     * @return {@code true} if the operation was successful
     */
    public boolean setPreferredInputDeviceForCalls(@Nullable CachedBluetoothDevice hearingDevice,
            @RoutingValue int routingValue) {
        AudioDeviceAttributes hearingDeviceAttributes = getMatchedHearingDeviceAttributesInput(
                hearingDevice);
        if (hearingDeviceAttributes == null) {
            Log.w(TAG, "Can not find expected input AudioDeviceAttributes for hearing device: "
                    + hearingDevice.getDevice().getAnonymizedAddress());
            return false;
        }

        final int audioSource = MICROPHONE_SOURCE_VOICE_COMMUNICATION;
        return switch (routingValue) {
            case RoutingValue.AUTO ->
                    mAudioManager.clearPreferredDevicesForCapturePreset(audioSource);
            case RoutingValue.HEARING_DEVICE -> {
                mAudioManager.clearPreferredDevicesForCapturePreset(audioSource);
                yield mAudioManager.setPreferredDeviceForCapturePreset(audioSource,
                        hearingDeviceAttributes);
            }
            case RoutingValue.BUILTIN_DEVICE -> {
                mAudioManager.clearPreferredDevicesForCapturePreset(audioSource);
                yield mAudioManager.setPreferredDeviceForCapturePreset(audioSource, BUILTIN_MIC);
            }
            default -> throw new IllegalArgumentException(
                    "Unexpected routingValue: " + routingValue);
        };
    }

    /**
     * Clears the preferred input device for calls.
     *
     * {@code true} if the operation was successful
     */
    public boolean clearPreferredInputDeviceForCalls() {
        return mAudioManager.clearPreferredDevicesForCapturePreset(
                MICROPHONE_SOURCE_VOICE_COMMUNICATION);
    }

    /**
     * Gets the matched output hearing device {@link AudioDeviceAttributes} for {@code device}.
     *
     * <p>Will also try to match the {@link CachedBluetoothDevice#getSubDevice()} and
     * {@link CachedBluetoothDevice#getMemberDevice()} of {@code device}
     *
     * @param device the {@link CachedBluetoothDevice} need to be hearing aid device
     * @return the requested AudioDeviceAttributes or {@code null} if not match
     */
    @Nullable
    public AudioDeviceAttributes getMatchedHearingDeviceAttributes(CachedBluetoothDevice device) {
    public AudioDeviceAttributes getMatchedHearingDeviceAttributesForOutput(
            @Nullable CachedBluetoothDevice device) {
        if (device == null || !device.isHearingAidDevice()) {
            return null;
        }

        AudioDeviceInfo[] audioDevices = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
        for (AudioDeviceInfo audioDevice : audioDevices) {
            //TODO: b/370812132 - Need to update if TYPE_LEA_HEARING_AID is added
            // ASHA for TYPE_HEARING_AID, HAP for TYPE_BLE_HEADSET
            if (audioDevice.getType() == AudioDeviceInfo.TYPE_HEARING_AID
                    || audioDevice.getType() == AudioDeviceInfo.TYPE_BLE_HEADSET) {
@@ -126,6 +189,35 @@ public class HearingAidAudioRoutingHelper {
        return null;
    }

    /**
     * Gets the matched input hearing device {@link AudioDeviceAttributes} for {@code device}.
     *
     * <p>Will also try to match the {@link CachedBluetoothDevice#getSubDevice()} and
     * {@link CachedBluetoothDevice#getMemberDevice()} of {@code device}
     *
     * @param device the {@link CachedBluetoothDevice} need to be hearing aid device
     * @return the requested AudioDeviceAttributes or {@code null} if not match
     */
    @Nullable
    private AudioDeviceAttributes getMatchedHearingDeviceAttributesInput(
            @Nullable CachedBluetoothDevice device) {
        if (device == null || !device.isHearingAidDevice()) {
            return null;
        }

        AudioDeviceInfo[] audioDevices = mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS);
        for (AudioDeviceInfo audioDevice : audioDevices) {
            //TODO: b/370812132 - Need to update if TYPE_LEA_HEARING_AID is added
            // HAP for TYPE_BLE_HEADSET
            if (audioDevice.getType() == AudioDeviceInfo.TYPE_BLE_HEADSET) {
                if (matchAddress(device, audioDevice)) {
                    return new AudioDeviceAttributes(audioDevice);
                }
            }
        }
        return null;
    }

    private boolean matchAddress(CachedBluetoothDevice device, AudioDeviceInfo audioDevice) {
        final String audioDeviceAddress = audioDevice.getAddress();
        final CachedBluetoothDevice subDevice = device.getSubDevice();
@@ -142,7 +234,6 @@ public class HearingAidAudioRoutingHelper {
        boolean status = true;
        for (AudioProductStrategy strategy : strategies) {
            status &= mAudioManager.setPreferredDeviceForStrategy(strategy, audioDevice);

        }

        return status;
+35 −16
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import android.util.FeatureFlagUtils;
import android.util.Log;

import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.bluetooth.HearingAidAudioRoutingConstants.RoutingValue;

import java.util.HashSet;
import java.util.List;
@@ -277,13 +278,19 @@ public class HearingAidDeviceManager {

    void onActiveDeviceChanged(CachedBluetoothDevice device) {
        if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_AUDIO_ROUTING)) {
            if (device.isActiveDevice(BluetoothProfile.HEARING_AID) || device.isActiveDevice(
                    BluetoothProfile.LE_AUDIO)) {
            if (device.isConnectedHearingAidDevice()) {
                setAudioRoutingConfig(device);
            } else {
                clearAudioRoutingConfig();
            }
        }
        if (com.android.settingslib.flags.Flags.hearingDevicesInputRoutingControl()) {
            if (device.isConnectedHearingAidDevice()) {
                setMicrophoneForCalls(device);
            } else {
                clearMicrophoneForCalls();
            }
        }
    }

    void syncDeviceIfNeeded(CachedBluetoothDevice device) {
@@ -311,9 +318,25 @@ public class HearingAidDeviceManager {
        HearingDeviceLocalDataManager.clear(mContext, device.getDevice());
    }

    private void setMicrophoneForCalls(CachedBluetoothDevice device) {
        boolean useRemoteMicrophone = device.getDevice().isMicrophonePreferredForCalls();
        boolean status = mRoutingHelper.setPreferredInputDeviceForCalls(device,
                useRemoteMicrophone ? RoutingValue.AUTO : RoutingValue.BUILTIN_DEVICE);
        if (!status) {
            Log.d(TAG, "Fail to configure setPreferredInputDeviceForCalls");
        }
    }

    private void clearMicrophoneForCalls() {
        boolean status = mRoutingHelper.clearPreferredInputDeviceForCalls();
        if (!status) {
            Log.d(TAG, "Fail to configure clearMicrophoneForCalls");
        }
    }

    private void setAudioRoutingConfig(CachedBluetoothDevice device) {
        AudioDeviceAttributes hearingDeviceAttributes =
                mRoutingHelper.getMatchedHearingDeviceAttributes(device);
                mRoutingHelper.getMatchedHearingDeviceAttributesForOutput(device);
        if (hearingDeviceAttributes == null) {
            Log.w(TAG, "Can not find expected AudioDeviceAttributes for hearing device: "
                    + device.getDevice().getAnonymizedAddress());
@@ -321,17 +344,13 @@ public class HearingAidDeviceManager {
        }

        final int callRoutingValue = Settings.Secure.getInt(mContentResolver,
                Settings.Secure.HEARING_AID_CALL_ROUTING,
                HearingAidAudioRoutingConstants.RoutingValue.AUTO);
                Settings.Secure.HEARING_AID_CALL_ROUTING, RoutingValue.AUTO);
        final int mediaRoutingValue = Settings.Secure.getInt(mContentResolver,
                Settings.Secure.HEARING_AID_MEDIA_ROUTING,
                HearingAidAudioRoutingConstants.RoutingValue.AUTO);
                Settings.Secure.HEARING_AID_MEDIA_ROUTING, RoutingValue.AUTO);
        final int ringtoneRoutingValue = Settings.Secure.getInt(mContentResolver,
                Settings.Secure.HEARING_AID_RINGTONE_ROUTING,
                HearingAidAudioRoutingConstants.RoutingValue.AUTO);
                Settings.Secure.HEARING_AID_RINGTONE_ROUTING, RoutingValue.AUTO);
        final int systemSoundsRoutingValue = Settings.Secure.getInt(mContentResolver,
                Settings.Secure.HEARING_AID_NOTIFICATION_ROUTING,
                HearingAidAudioRoutingConstants.RoutingValue.AUTO);
                Settings.Secure.HEARING_AID_NOTIFICATION_ROUTING, RoutingValue.AUTO);

        setPreferredDeviceRoutingStrategies(
                HearingAidAudioRoutingConstants.CALL_ROUTING_ATTRIBUTES,
@@ -351,21 +370,21 @@ public class HearingAidDeviceManager {
        // Don't need to pass hearingDevice when we want to reset it (set to AUTO).
        setPreferredDeviceRoutingStrategies(
                HearingAidAudioRoutingConstants.CALL_ROUTING_ATTRIBUTES,
                /* hearingDevice = */ null, HearingAidAudioRoutingConstants.RoutingValue.AUTO);
                /* hearingDevice = */ null, RoutingValue.AUTO);
        setPreferredDeviceRoutingStrategies(
                HearingAidAudioRoutingConstants.MEDIA_ROUTING_ATTRIBUTES,
                /* hearingDevice = */ null, HearingAidAudioRoutingConstants.RoutingValue.AUTO);
                /* hearingDevice = */ null, RoutingValue.AUTO);
        setPreferredDeviceRoutingStrategies(
                HearingAidAudioRoutingConstants.RINGTONE_ROUTING_ATTRIBUTES,
                /* hearingDevice = */ null, HearingAidAudioRoutingConstants.RoutingValue.AUTO);
                /* hearingDevice = */ null, RoutingValue.AUTO);
        setPreferredDeviceRoutingStrategies(
                HearingAidAudioRoutingConstants.NOTIFICATION_ROUTING_ATTRIBUTES,
                /* hearingDevice = */ null, HearingAidAudioRoutingConstants.RoutingValue.AUTO);
                /* hearingDevice = */ null, RoutingValue.AUTO);
    }

    private void setPreferredDeviceRoutingStrategies(int[] attributeSdkUsageList,
            AudioDeviceAttributes hearingDevice,
            @HearingAidAudioRoutingConstants.RoutingValue int routingValue) {
            @RoutingValue int routingValue) {
        final List<AudioProductStrategy> supportedStrategies =
                mRoutingHelper.getSupportedStrategies(attributeSdkUsageList);

+75 −30

File changed.

Preview size limit exceeded, changes collapsed.

+2 −2
Original line number Diff line number Diff line
@@ -729,7 +729,7 @@ public class HearingAidDeviceManagerTest {

    @Test
    public void onActiveDeviceChanged_connected_callSetStrategies() {
        when(mHelper.getMatchedHearingDeviceAttributes(mCachedDevice1)).thenReturn(
        when(mHelper.getMatchedHearingDeviceAttributesForOutput(mCachedDevice1)).thenReturn(
                mHearingDeviceAttribute);
        when(mCachedDevice1.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(true);
        doReturn(true).when(mHelper).setPreferredDeviceRoutingStrategies(anyList(),
@@ -743,7 +743,7 @@ public class HearingAidDeviceManagerTest {

    @Test
    public void onActiveDeviceChanged_disconnected_callSetStrategiesWithAutoValue() {
        when(mHelper.getMatchedHearingDeviceAttributes(mCachedDevice1)).thenReturn(
        when(mHelper.getMatchedHearingDeviceAttributesForOutput(mCachedDevice1)).thenReturn(
                mHearingDeviceAttribute);
        when(mCachedDevice1.isActiveDevice(BluetoothProfile.HEARING_AID)).thenReturn(false);
        doReturn(true).when(mHelper).setPreferredDeviceRoutingStrategies(anyList(), any(),