Fix audio routing can not work after the phone get rebooted

Root Cause: setPreferredDeviceForStrategy() need to be called when hearing device get connected.

Solution:
 * Call setPreferredDeviceForStrategy() when HearingAidDeviceManager#onProfileConnectionStateChangedIfProcessed()
 * Extract the common functions into SettingsLib

Bug: 269122580
Test: make RunSettingsRoboTests
ROBOTEST_FILTER="(CachedBluetoothDeviceManagerTest|HearingAidAudioRoutingHelperTest|HearingAidDeviceManagerTest)"

Change-Id: I622040384f56f61c65f0daac45735789d637b703
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index f741f65..7b4c862 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -51,7 +51,8 @@
     public CachedBluetoothDeviceManager(Context context, LocalBluetoothManager localBtManager) {
         mContext = context;
         mBtManager = localBtManager;
-        mHearingAidDeviceManager = new HearingAidDeviceManager(localBtManager, mCachedDevices);
+        mHearingAidDeviceManager = new HearingAidDeviceManager(context, localBtManager,
+                mCachedDevices);
         mCsipDeviceManager = new CsipDeviceManager(localBtManager, mCachedDevices);
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingConstants.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingConstants.java
new file mode 100644
index 0000000..d8475b3
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingConstants.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import android.media.AudioAttributes;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
+
+import androidx.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Constant values used to configure hearing aid audio routing.
+ *
+ * {@link HearingAidAudioRoutingHelper}
+ */
+public final class HearingAidAudioRoutingConstants {
+    public static final int[] CALL_ROUTING_ATTRIBUTES = new int[] {
+            // Stands for STRATEGY_PHONE
+            AudioAttributes.USAGE_VOICE_COMMUNICATION,
+    };
+
+    public static final int[] MEDIA_ROUTING_ATTRIBUTES = new int[] {
+            // Stands for STRATEGY_MEDIA, including USAGE_GAME, USAGE_ASSISTANT,
+            // USAGE_ASSISTANCE_NAVIGATION_GUIDANCE, USAGE_ASSISTANCE_SONIFICATION
+            AudioAttributes.USAGE_MEDIA
+    };
+
+    public static final int[] RINGTONE_ROUTING_ATTRIBUTE = new int[] {
+            // Stands for STRATEGY_SONIFICATION, including USAGE_ALARM
+            AudioAttributes.USAGE_NOTIFICATION_RINGTONE
+    };
+
+    public static final int[] SYSTEM_SOUNDS_ROUTING_ATTRIBUTES = new int[] {
+            // Stands for STRATEGY_SONIFICATION_RESPECTFUL, including USAGE_NOTIFICATION_EVENT
+            AudioAttributes.USAGE_NOTIFICATION,
+            // Stands for STRATEGY_ACCESSIBILITY
+            AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY,
+            // Stands for STRATEGY_DTMF
+            AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING,
+    };
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            RoutingValue.AUTO,
+            RoutingValue.HEARING_DEVICE,
+            RoutingValue.DEVICE_SPEAKER,
+    })
+
+    public @interface RoutingValue {
+        int AUTO = 0;
+        int HEARING_DEVICE = 1;
+        int DEVICE_SPEAKER = 2;
+    }
+
+    public static final AudioDeviceAttributes DEVICE_SPEAKER_OUT = new AudioDeviceAttributes(
+            AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, "");
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelper.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelper.java
new file mode 100644
index 0000000..c9512cd
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelper.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+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 androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+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.
+ */
+public class HearingAidAudioRoutingHelper {
+
+    private final AudioManager mAudioManager;
+
+    public HearingAidAudioRoutingHelper(Context context) {
+        mAudioManager = context.getSystemService(AudioManager.class);
+    }
+
+    /**
+     * Gets the list of {@link AudioProductStrategy} referred by the given list of usage values
+     * defined in {@link AudioAttributes}
+     */
+    public List<AudioProductStrategy> getSupportedStrategies(int[] attributeSdkUsageList) {
+        final List<AudioAttributes> audioAttrList = new ArrayList<>(attributeSdkUsageList.length);
+        for (int attributeSdkUsage : attributeSdkUsageList) {
+            audioAttrList.add(new AudioAttributes.Builder().setUsage(attributeSdkUsage).build());
+        }
+
+        final List<AudioProductStrategy> allStrategies = getAudioProductStrategies();
+        final List<AudioProductStrategy> supportedStrategies = new ArrayList<>();
+        for (AudioProductStrategy strategy : allStrategies) {
+            for (AudioAttributes audioAttr : audioAttrList) {
+                if (strategy.supportsAudioAttributes(audioAttr)) {
+                    supportedStrategies.add(strategy);
+                }
+            }
+        }
+
+        return supportedStrategies.stream().distinct().collect(Collectors.toList());
+    }
+
+    /**
+     * Sets the preferred device for the given strategies.
+     *
+     * @param supportedStrategies A list of {@link AudioProductStrategy} used to configure audio
+     *                            routing
+     * @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
+     *                     destination.
+     * @return {code true} if the routing value successfully configure
+     */
+    public boolean setPreferredDeviceRoutingStrategies(
+            List<AudioProductStrategy> supportedStrategies, AudioDeviceAttributes hearingDevice,
+            @HearingAidAudioRoutingConstants.RoutingValue int routingValue) {
+        boolean status;
+        switch (routingValue) {
+            case HearingAidAudioRoutingConstants.RoutingValue.AUTO:
+                status = removePreferredDeviceForStrategies(supportedStrategies);
+                return status;
+            case HearingAidAudioRoutingConstants.RoutingValue.HEARING_DEVICE:
+                status = removePreferredDeviceForStrategies(supportedStrategies);
+                status &= setPreferredDeviceForStrategies(supportedStrategies, hearingDevice);
+                return status;
+            case HearingAidAudioRoutingConstants.RoutingValue.DEVICE_SPEAKER:
+                status = removePreferredDeviceForStrategies(supportedStrategies);
+                status &= setPreferredDeviceForStrategies(supportedStrategies,
+                        HearingAidAudioRoutingConstants.DEVICE_SPEAKER_OUT);
+                return status;
+            default:
+                throw new IllegalArgumentException("Unexpected routingValue: " + routingValue);
+        }
+    }
+
+    /**
+     * Gets the matched hearing device {@link AudioDeviceAttributes} for {@code device}.
+     *
+     * <p>Will also try to match the {@link CachedBluetoothDevice#getSubDevice()} 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) {
+        if (device == null || !device.isHearingAidDevice()) {
+            return null;
+        }
+
+        AudioDeviceInfo[] audioDevices = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
+        for (AudioDeviceInfo audioDevice : audioDevices) {
+            // ASHA for TYPE_HEARING_AID, HAP for TYPE_BLE_HEADSET
+            if (audioDevice.getType() == AudioDeviceInfo.TYPE_HEARING_AID
+                    || 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();
+        final Set<CachedBluetoothDevice> memberDevices = device.getMemberDevice();
+
+        return device.getAddress().equals(audioDeviceAddress)
+                || (subDevice != null && subDevice.getAddress().equals(audioDeviceAddress))
+                || (!memberDevices.isEmpty() && memberDevices.stream().anyMatch(
+                    m -> m.getAddress().equals(audioDeviceAddress)));
+    }
+
+    private boolean setPreferredDeviceForStrategies(List<AudioProductStrategy> strategies,
+            AudioDeviceAttributes audioDevice) {
+        boolean status = true;
+        for (AudioProductStrategy strategy : strategies) {
+            status &= mAudioManager.setPreferredDeviceForStrategy(strategy, audioDevice);
+
+        }
+
+        return status;
+    }
+
+    private boolean removePreferredDeviceForStrategies(List<AudioProductStrategy> strategies) {
+        boolean status = true;
+        for (AudioProductStrategy strategy : strategies) {
+            status &= mAudioManager.removePreferredDeviceForStrategy(strategy);
+        }
+
+        return status;
+    }
+
+    @VisibleForTesting
+    public List<AudioProductStrategy> getAudioProductStrategies() {
+        return AudioManager.getAudioProductStrategies();
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
index ebfec0a..4354e0c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidDeviceManager.java
@@ -18,6 +18,11 @@
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothHearingAid;
 import android.bluetooth.BluetoothProfile;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.media.AudioDeviceAttributes;
+import android.media.audiopolicy.AudioProductStrategy;
+import android.provider.Settings;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -33,12 +38,25 @@
     private static final String TAG = "HearingAidDeviceManager";
     private static final boolean DEBUG = BluetoothUtils.D;
 
+    private final ContentResolver mContentResolver;
     private final LocalBluetoothManager mBtManager;
     private final List<CachedBluetoothDevice> mCachedDevices;
-    HearingAidDeviceManager(LocalBluetoothManager localBtManager,
+    private final HearingAidAudioRoutingHelper mRoutingHelper;
+    HearingAidDeviceManager(Context context, LocalBluetoothManager localBtManager,
             List<CachedBluetoothDevice> CachedDevices) {
+        mContentResolver = context.getContentResolver();
         mBtManager = localBtManager;
         mCachedDevices = CachedDevices;
+        mRoutingHelper = new HearingAidAudioRoutingHelper(context);
+    }
+
+    @VisibleForTesting
+    HearingAidDeviceManager(Context context, LocalBluetoothManager localBtManager,
+            List<CachedBluetoothDevice> cachedDevices, HearingAidAudioRoutingHelper routingHelper) {
+        mContentResolver = context.getContentResolver();
+        mBtManager = localBtManager;
+        mCachedDevices = cachedDevices;
+        mRoutingHelper = routingHelper;
     }
 
     void initHearingAidDeviceIfNeeded(CachedBluetoothDevice newDevice) {
@@ -192,12 +210,11 @@
             case BluetoothProfile.STATE_CONNECTED:
                 onHiSyncIdChanged(cachedDevice.getHiSyncId());
                 CachedBluetoothDevice mainDevice = findMainDevice(cachedDevice);
-                if (mainDevice != null){
+                if (mainDevice != null) {
                     if (mainDevice.isConnected()) {
                         // When main device exists and in connected state, receiving sub device
                         // connection. To refresh main device UI
                         mainDevice.refresh();
-                        return true;
                     } else {
                         // When both Hearing Aid devices are disconnected, receiving sub device
                         // connection. To switch content and dispatch to notify UI change
@@ -207,9 +224,15 @@
                         // It is necessary to do remove and add for updating the mapping on
                         // preference and device
                         mBtManager.getEventManager().dispatchDeviceAdded(mainDevice);
-                        return true;
+                        // Only need to set first device of a set. AudioDeviceInfo for
+                        // GET_DEVICES_OUTPUTS will not change device.
+                        setAudioRoutingConfig(cachedDevice);
                     }
+                    return true;
                 }
+                // Only need to set first device of a set. AudioDeviceInfo for GET_DEVICES_OUTPUTS
+                // will not change device.
+                setAudioRoutingConfig(cachedDevice);
                 break;
             case BluetoothProfile.STATE_DISCONNECTED:
                 mainDevice = findMainDevice(cachedDevice);
@@ -232,13 +255,83 @@
                     // It is necessary to do remove and add for updating the mapping on
                     // preference and device
                     mBtManager.getEventManager().dispatchDeviceAdded(cachedDevice);
+
                     return true;
                 }
+                // Only need to clear when last device of a set get disconnected
+                clearAudioRoutingConfig();
                 break;
         }
         return false;
     }
 
+    private void setAudioRoutingConfig(CachedBluetoothDevice device) {
+        AudioDeviceAttributes hearingDeviceAttributes =
+                mRoutingHelper.getMatchedHearingDeviceAttributes(device);
+        if (hearingDeviceAttributes == null) {
+            Log.w(TAG, "Can not find expected AudioDeviceAttributes for hearing device: "
+                    + device.getDevice().getAnonymizedAddress());
+            return;
+        }
+
+        final int callRoutingValue = Settings.Secure.getInt(mContentResolver,
+                Settings.Secure.HEARING_AID_CALL_ROUTING,
+                HearingAidAudioRoutingConstants.RoutingValue.AUTO);
+        final int mediaRoutingValue = Settings.Secure.getInt(mContentResolver,
+                Settings.Secure.HEARING_AID_MEDIA_ROUTING,
+                HearingAidAudioRoutingConstants.RoutingValue.AUTO);
+        final int ringtoneRoutingValue = Settings.Secure.getInt(mContentResolver,
+                Settings.Secure.HEARING_AID_RINGTONE_ROUTING,
+                HearingAidAudioRoutingConstants.RoutingValue.AUTO);
+        final int systemSoundsRoutingValue = Settings.Secure.getInt(mContentResolver,
+                Settings.Secure.HEARING_AID_SYSTEM_SOUNDS_ROUTING,
+                HearingAidAudioRoutingConstants.RoutingValue.AUTO);
+
+        setPreferredDeviceRoutingStrategies(
+                HearingAidAudioRoutingConstants.CALL_ROUTING_ATTRIBUTES,
+                hearingDeviceAttributes, callRoutingValue);
+        setPreferredDeviceRoutingStrategies(
+                HearingAidAudioRoutingConstants.MEDIA_ROUTING_ATTRIBUTES,
+                hearingDeviceAttributes, mediaRoutingValue);
+        setPreferredDeviceRoutingStrategies(
+                HearingAidAudioRoutingConstants.RINGTONE_ROUTING_ATTRIBUTE,
+                hearingDeviceAttributes, ringtoneRoutingValue);
+        setPreferredDeviceRoutingStrategies(
+                HearingAidAudioRoutingConstants.SYSTEM_SOUNDS_ROUTING_ATTRIBUTES,
+                hearingDeviceAttributes, systemSoundsRoutingValue);
+    }
+
+    private void clearAudioRoutingConfig() {
+        // 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);
+        setPreferredDeviceRoutingStrategies(
+                HearingAidAudioRoutingConstants.MEDIA_ROUTING_ATTRIBUTES,
+                /* hearingDevice = */ null, HearingAidAudioRoutingConstants.RoutingValue.AUTO);
+        setPreferredDeviceRoutingStrategies(
+                HearingAidAudioRoutingConstants.RINGTONE_ROUTING_ATTRIBUTE,
+                /* hearingDevice = */ null, HearingAidAudioRoutingConstants.RoutingValue.AUTO);
+        setPreferredDeviceRoutingStrategies(
+                HearingAidAudioRoutingConstants.SYSTEM_SOUNDS_ROUTING_ATTRIBUTES,
+                /* hearingDevice = */ null, HearingAidAudioRoutingConstants.RoutingValue.AUTO);
+    }
+
+    private void setPreferredDeviceRoutingStrategies(int[] attributeSdkUsageList,
+            AudioDeviceAttributes hearingDevice,
+            @HearingAidAudioRoutingConstants.RoutingValue int routingValue) {
+        final List<AudioProductStrategy> supportedStrategies =
+                mRoutingHelper.getSupportedStrategies(attributeSdkUsageList);
+
+        final boolean status = mRoutingHelper.setPreferredDeviceRoutingStrategies(
+                supportedStrategies, hearingDevice, routingValue);
+
+        if (!status) {
+            Log.w(TAG, "routingStrategies: " + supportedStrategies.toString() + "routingValue: "
+                    + routingValue + " fail to configure AudioProductStrategy");
+        }
+    }
+
     CachedBluetoothDevice findMainDevice(CachedBluetoothDevice device) {
         for (CachedBluetoothDevice cachedDevice : mCachedDevices) {
             if (isValidHiSyncId(cachedDevice.getHiSyncId())) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index a3c2e70c7..43e3a32 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -361,6 +361,7 @@
                         cachedDevice.setHearingAidInfo(infoBuilder.build());
                     }
                 }
+
                 HearingAidStatsLogUtils.logHearingAidInfo(cachedDevice);
             }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
index f06623d..4b3820e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java
@@ -403,7 +403,7 @@
      */
     @Test
     public void updateHearingAidDevices_directToHearingAidDeviceManager() {
-        mHearingAidDeviceManager = spy(new HearingAidDeviceManager(mLocalBluetoothManager,
+        mHearingAidDeviceManager = spy(new HearingAidDeviceManager(mContext, mLocalBluetoothManager,
                 mCachedDeviceManager.mCachedDevices));
         mCachedDeviceManager.mHearingAidDeviceManager = mHearingAidDeviceManager;
         mCachedDeviceManager.updateHearingAidsDevices();
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelperTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelperTest.java
new file mode 100644
index 0000000..8b5ea30
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelperTest.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+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 androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/** Tests for {@link HearingAidAudioRoutingHelper}. */
+@RunWith(RobolectricTestRunner.class)
+public class HearingAidAudioRoutingHelperTest {
+
+    @Rule
+    public MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+    @Spy
+    private final Context mContext = ApplicationProvider.getApplicationContext();
+    private static final String TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1";
+    private static final String NOT_EXPECT_DEVICE_ADDRESS = "11:B2:B2:B2:B2:B2";
+
+    @Mock
+    private AudioProductStrategy mAudioStrategy;
+    @Spy
+    private AudioManager mAudioManager = mContext.getSystemService(AudioManager.class);
+    @Mock
+    private AudioDeviceInfo mAudioDeviceInfo;
+    @Mock
+    private CachedBluetoothDevice mCachedBluetoothDevice;
+    @Mock
+    private CachedBluetoothDevice mSubCachedBluetoothDevice;
+    private AudioDeviceAttributes mHearingDeviceAttribute;
+    private HearingAidAudioRoutingHelper mHelper;
+
+    @Before
+    public void setUp() {
+        doReturn(mAudioManager).when(mContext).getSystemService(AudioManager.class);
+        when(mAudioDeviceInfo.getType()).thenReturn(AudioDeviceInfo.TYPE_HEARING_AID);
+        when(mAudioDeviceInfo.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
+        when(mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)).thenReturn(
+                new AudioDeviceInfo[]{mAudioDeviceInfo});
+        when(mAudioStrategy.getAudioAttributesForLegacyStreamType(
+                AudioManager.STREAM_MUSIC))
+                .thenReturn((new AudioAttributes.Builder()).build());
+
+        mHearingDeviceAttribute = new AudioDeviceAttributes(
+                AudioDeviceAttributes.ROLE_OUTPUT,
+                AudioDeviceInfo.TYPE_HEARING_AID,
+                TEST_DEVICE_ADDRESS);
+        mHelper = spy(new HearingAidAudioRoutingHelper(mContext));
+        doReturn(List.of(mAudioStrategy)).when(mHelper).getAudioProductStrategies();
+    }
+
+    @Test
+    public void setPreferredDeviceRoutingStrategies_valueAuto_callRemoveStrategy() {
+        mHelper.setPreferredDeviceRoutingStrategies(List.of(mAudioStrategy),
+                mHearingDeviceAttribute,
+                HearingAidAudioRoutingConstants.RoutingValue.AUTO);
+
+        verify(mAudioManager, atLeastOnce()).removePreferredDeviceForStrategy(mAudioStrategy);
+    }
+
+    @Test
+    public void setPreferredDeviceRoutingStrategies_valueHearingDevice_callSetStrategy() {
+        mHelper.setPreferredDeviceRoutingStrategies(List.of(mAudioStrategy),
+                mHearingDeviceAttribute,
+                HearingAidAudioRoutingConstants.RoutingValue.HEARING_DEVICE);
+
+        verify(mAudioManager, atLeastOnce()).setPreferredDeviceForStrategy(mAudioStrategy,
+                mHearingDeviceAttribute);
+    }
+
+    @Test
+    public void setPreferredDeviceRoutingStrategies_valueDeviceSpeaker_callSetStrategy() {
+        final AudioDeviceAttributes speakerDevice = new AudioDeviceAttributes(
+                AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, "");
+        mHelper.setPreferredDeviceRoutingStrategies(List.of(mAudioStrategy),
+                mHearingDeviceAttribute,
+                HearingAidAudioRoutingConstants.RoutingValue.DEVICE_SPEAKER);
+
+        verify(mAudioManager, atLeastOnce()).setPreferredDeviceForStrategy(mAudioStrategy,
+                speakerDevice);
+    }
+
+    @Test
+    public void getMatchedHearingDeviceAttributes_mainHearingDevice_equalAddress() {
+        when(mCachedBluetoothDevice.isHearingAidDevice()).thenReturn(true);
+        when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
+
+        final String targetAddress = mHelper.getMatchedHearingDeviceAttributes(
+                mCachedBluetoothDevice).getAddress();
+
+        assertThat(targetAddress).isEqualTo(mHearingDeviceAttribute.getAddress());
+    }
+
+    @Test
+    public void getMatchedHearingDeviceAttributes_subHearingDevice_equalAddress() {
+        when(mCachedBluetoothDevice.isHearingAidDevice()).thenReturn(true);
+        when(mCachedBluetoothDevice.getAddress()).thenReturn(NOT_EXPECT_DEVICE_ADDRESS);
+        when(mCachedBluetoothDevice.getSubDevice()).thenReturn(mSubCachedBluetoothDevice);
+        when(mSubCachedBluetoothDevice.isHearingAidDevice()).thenReturn(true);
+        when(mSubCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
+
+        final String targetAddress = mHelper.getMatchedHearingDeviceAttributes(
+                mCachedBluetoothDevice).getAddress();
+
+        assertThat(targetAddress).isEqualTo(mHearingDeviceAttribute.getAddress());
+    }
+
+    @Test
+    public void getMatchedHearingDeviceAttributes_memberHearingDevice_equalAddress() {
+        when(mSubCachedBluetoothDevice.isHearingAidDevice()).thenReturn(true);
+        when(mSubCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
+        final Set<CachedBluetoothDevice> memberDevices = new HashSet<CachedBluetoothDevice>();
+        memberDevices.add(mSubCachedBluetoothDevice);
+        when(mCachedBluetoothDevice.isHearingAidDevice()).thenReturn(true);
+        when(mCachedBluetoothDevice.getAddress()).thenReturn(NOT_EXPECT_DEVICE_ADDRESS);
+        when(mCachedBluetoothDevice.getMemberDevice()).thenReturn(memberDevices);
+
+        final String targetAddress = mHelper.getMatchedHearingDeviceAttributes(
+                mCachedBluetoothDevice).getAddress();
+
+        assertThat(targetAddress).isEqualTo(mHearingDeviceAttribute.getAddress());
+    }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
index 470d8e0..a839136 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidDeviceManagerTest.java
@@ -18,7 +18,12 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
@@ -29,18 +34,32 @@
 import android.bluetooth.BluetoothHearingAid;
 import android.bluetooth.BluetoothProfile;
 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.os.Parcel;
 
+import androidx.test.core.app.ApplicationProvider;
+
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
+
+import java.util.List;
 
 @RunWith(RobolectricTestRunner.class)
 public class HearingAidDeviceManagerTest {
+    @Rule
+    public MockitoRule mMockitoRule = MockitoJUnit.rule();
+
     private final static long HISYNCID1 = 10;
     private final static long HISYNCID2 = 11;
     private final static String DEVICE_NAME_1 = "TestName_1";
@@ -51,6 +70,15 @@
     private final static String DEVICE_ADDRESS_2 = "AA:BB:CC:DD:EE:22";
     private final BluetoothClass DEVICE_CLASS =
             createBtClass(BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE);
+
+    private CachedBluetoothDevice mCachedDevice1;
+    private CachedBluetoothDevice mCachedDevice2;
+    private CachedBluetoothDeviceManager mCachedDeviceManager;
+    private HearingAidDeviceManager mHearingAidDeviceManager;
+    private AudioDeviceAttributes mHearingDeviceAttribute;
+    private final Context mContext = ApplicationProvider.getApplicationContext();
+    @Spy
+    private HearingAidAudioRoutingHelper mHelper = new HearingAidAudioRoutingHelper(mContext);
     @Mock
     private LocalBluetoothProfileManager mLocalProfileManager;
     @Mock
@@ -60,14 +88,12 @@
     @Mock
     private HearingAidProfile mHearingAidProfile;
     @Mock
+    private AudioProductStrategy mAudioStrategy;
+    @Mock
     private BluetoothDevice mDevice1;
     @Mock
     private BluetoothDevice mDevice2;
-    private CachedBluetoothDevice mCachedDevice1;
-    private CachedBluetoothDevice mCachedDevice2;
-    private CachedBluetoothDeviceManager mCachedDeviceManager;
-    private HearingAidDeviceManager mHearingAidDeviceManager;
-    private Context mContext;
+
 
     private BluetoothClass createBtClass(int deviceClass) {
         Parcel p = Parcel.obtain();
@@ -81,8 +107,6 @@
 
     @Before
     public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        mContext = RuntimeEnvironment.application;
         when(mDevice1.getAddress()).thenReturn(DEVICE_ADDRESS_1);
         when(mDevice2.getAddress()).thenReturn(DEVICE_ADDRESS_2);
         when(mDevice1.getName()).thenReturn(DEVICE_NAME_1);
@@ -94,10 +118,18 @@
         when(mLocalBluetoothManager.getEventManager()).thenReturn(mBluetoothEventManager);
         when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalProfileManager);
         when(mLocalProfileManager.getHearingAidProfile()).thenReturn(mHearingAidProfile);
+        when(mAudioStrategy.getAudioAttributesForLegacyStreamType(
+                AudioManager.STREAM_MUSIC))
+                .thenReturn((new AudioAttributes.Builder()).build());
+        doReturn(List.of(mAudioStrategy)).when(mHelper).getSupportedStrategies(any(int[].class));
 
+        mHearingDeviceAttribute = new AudioDeviceAttributes(
+                AudioDeviceAttributes.ROLE_OUTPUT,
+                AudioDeviceInfo.TYPE_HEARING_AID,
+                DEVICE_ADDRESS_1);
         mCachedDeviceManager = new CachedBluetoothDeviceManager(mContext, mLocalBluetoothManager);
-        mHearingAidDeviceManager = spy(new HearingAidDeviceManager(mLocalBluetoothManager,
-                mCachedDeviceManager.mCachedDevices));
+        mHearingAidDeviceManager = spy(new HearingAidDeviceManager(mContext, mLocalBluetoothManager,
+                mCachedDeviceManager.mCachedDevices, mHelper));
         mCachedDevice1 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice1));
         mCachedDevice2 = spy(new CachedBluetoothDevice(mContext, mLocalProfileManager, mDevice2));
     }
@@ -446,6 +478,44 @@
     }
 
     @Test
+    public void onProfileConnectionStateChanged_connected_callSetStrategies() {
+        when(mHelper.getMatchedHearingDeviceAttributes(mCachedDevice1)).thenReturn(
+                mHearingDeviceAttribute);
+
+        mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(mCachedDevice1,
+                BluetoothProfile.STATE_CONNECTED);
+
+        verify(mHelper, atLeastOnce()).setPreferredDeviceRoutingStrategies(
+                eq(List.of(mAudioStrategy)), any(AudioDeviceAttributes.class), anyInt());
+    }
+
+    @Test
+    public void onProfileConnectionStateChanged_disconnected_callSetStrategiesWithAutoValue() {
+        when(mHelper.getMatchedHearingDeviceAttributes(mCachedDevice1)).thenReturn(
+                mHearingDeviceAttribute);
+
+        mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(mCachedDevice1,
+                BluetoothProfile.STATE_DISCONNECTED);
+
+        verify(mHelper, atLeastOnce()).setPreferredDeviceRoutingStrategies(
+                eq(List.of(mAudioStrategy)), /* hearingDevice= */ isNull(),
+                eq(HearingAidAudioRoutingConstants.RoutingValue.AUTO));
+    }
+    @Test
+    public void onProfileConnectionStateChanged_unpairing_callSetStrategiesWithAutoValue() {
+        when(mHelper.getMatchedHearingDeviceAttributes(mCachedDevice1)).thenReturn(
+                mHearingDeviceAttribute);
+
+        when(mCachedDevice1.getUnpairing()).thenReturn(true);
+        mHearingAidDeviceManager.onProfileConnectionStateChangedIfProcessed(mCachedDevice1,
+                BluetoothProfile.STATE_DISCONNECTED);
+
+        verify(mHelper, atLeastOnce()).setPreferredDeviceRoutingStrategies(
+                eq(List.of(mAudioStrategy)), /* hearingDevice= */ isNull(),
+                eq(HearingAidAudioRoutingConstants.RoutingValue.AUTO));
+    }
+
+    @Test
     public void findMainDevice() {
         when(mCachedDevice1.getHiSyncId()).thenReturn(HISYNCID1);
         when(mCachedDevice2.getHiSyncId()).thenReturn(HISYNCID1);