diff --git a/src/com/android/settings/bluetooth/HearingDeviceAudioRoutingBasePreferenceController.java b/src/com/android/settings/bluetooth/HearingDeviceAudioRoutingBasePreferenceController.java index 84412da905f..e93863ae064 100644 --- a/src/com/android/settings/bluetooth/HearingDeviceAudioRoutingBasePreferenceController.java +++ b/src/com/android/settings/bluetooth/HearingDeviceAudioRoutingBasePreferenceController.java @@ -20,24 +20,20 @@ import android.content.ContentResolver; 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.IntDef; -import androidx.annotation.VisibleForTesting; import androidx.preference.ListPreference; import androidx.preference.Preference; import com.android.settings.core.BasePreferenceController; import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.HearingAidAudioRoutingConstants; +import com.android.settingslib.bluetooth.HearingAidAudioRoutingHelper; +import com.google.common.annotations.VisibleForTesting; import com.google.common.primitives.Ints; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -49,16 +45,21 @@ public abstract class HearingDeviceAudioRoutingBasePreferenceController extends BasePreferenceController implements Preference.OnPreferenceChangeListener { private static final String TAG = "HARoutingBasePreferenceController"; + private static final boolean DEBUG = false; - private static final AudioDeviceAttributes DEVICE_SPEAKER_OUT = new AudioDeviceAttributes( - AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, ""); - - private final AudioManager mAudioManager; + private final HearingAidAudioRoutingHelper mHelper; public HearingDeviceAudioRoutingBasePreferenceController(Context context, String preferenceKey) { super(context, preferenceKey); - mAudioManager = mContext.getSystemService(AudioManager.class); + mHelper = new HearingAidAudioRoutingHelper(context); + } + + @VisibleForTesting + public HearingDeviceAudioRoutingBasePreferenceController(Context context, + String preferenceKey, HearingAidAudioRoutingHelper helper) { + super(context, preferenceKey); + mHelper = helper; } @Override @@ -77,48 +78,46 @@ public abstract class HearingDeviceAudioRoutingBasePreferenceController extends @Override public boolean onPreferenceChange(Preference preference, Object newValue) { - final ListPreference listPreference = (ListPreference) preference; final Integer routingValue = Ints.tryParse((String) newValue); - final AudioDeviceAttributes hearingDeviceAttribute = new AudioDeviceAttributes( - AudioDeviceAttributes.ROLE_OUTPUT, - AudioDeviceInfo.TYPE_HEARING_AID, - getHearingDevice().getAddress()); - final List supportedStrategies = getSupportedStrategies( - getSupportedAttributeList()); - - boolean status = false; - if (routingValue != null) { - switch (routingValue) { - case RoutingValue.AUTO: - status = removePreferredDeviceForStrategies(supportedStrategies); - break; - case RoutingValue.HEARING_DEVICE: - removePreferredDeviceForStrategies(supportedStrategies); - status = setPreferredDeviceForStrategies(supportedStrategies, - hearingDeviceAttribute); - break; - case RoutingValue.DEVICE_SPEAKER: - removePreferredDeviceForStrategies(supportedStrategies); - status = setPreferredDeviceForStrategies(supportedStrategies, - DEVICE_SPEAKER_OUT); - break; - default: - throw new IllegalArgumentException("Unexpected routingValue: " + routingValue); - } - } - if (!status) { - Log.w(TAG, "routingMode: " + listPreference.getKey() + "routingValue: " + routingValue - + " fail to configure AudioProductStrategy"); - } saveRoutingValue(mContext, routingValue); - updateState(listPreference); + trySetAudioRoutingConfig(getSupportedAttributeList(), getHearingDevice(), routingValue); + return true; } + private void trySetAudioRoutingConfig(int[] audioAttributes, + CachedBluetoothDevice hearingDevice, + @HearingAidAudioRoutingConstants.RoutingValue int routingValue) { + final List supportedStrategies = mHelper.getSupportedStrategies( + audioAttributes); + final AudioDeviceAttributes hearingDeviceAttributes = + mHelper.getMatchedHearingDeviceAttributes(hearingDevice); + if (hearingDeviceAttributes == null) { + if (DEBUG) { + Log.d(TAG, + "Can not find expected AudioDeviceAttributes to config audio routing " + + "maybe device is offline: " + + hearingDevice.getDevice().getAnonymizedAddress()); + } + return; + } + + final boolean status = mHelper.setPreferredDeviceRoutingStrategies(supportedStrategies, + hearingDeviceAttributes, routingValue); + + if (!status) { + final List strategiesName = supportedStrategies.stream() + .map(AudioProductStrategy::getName) + .collect(Collectors.toList()); + Log.w(TAG, "routingMode: " + strategiesName + " routingValue: " + routingValue + + " fail to configure AudioProductStrategy"); + } + } + /** - * Gets a list of usage value defined in {@link AudioAttributes} that is used to configure - * audio routing via {@link AudioProductStrategy}. + * Gets a list of usage values defined in {@link AudioAttributes} that are used to identify + * {@link AudioProductStrategy} to configure audio routing. */ protected abstract int[] getSupportedAttributeList(); @@ -129,77 +128,19 @@ public abstract class HearingDeviceAudioRoutingBasePreferenceController extends protected abstract CachedBluetoothDevice getHearingDevice(); /** - * Saves the {@link RoutingValue}. + * Saves the routing value. * * @param context the valid context used to get the {@link ContentResolver} - * @param routingValue the value defined in {@link RoutingValue} + * @param routingValue one of the value defined in + * {@link HearingAidAudioRoutingConstants.RoutingValue} */ protected abstract void saveRoutingValue(Context context, int routingValue); /** - * Restores the {@link RoutingValue} and used to reflect status on ListPreference. + * Restores the routing value and used to reflect status on ListPreference. * * @param context the valid context used to get the {@link ContentResolver} - * @return one of {@link RoutingValue} + * @return one of the value defined in {@link HearingAidAudioRoutingConstants.RoutingValue} */ protected abstract int restoreRoutingValue(Context context); - - private List getSupportedStrategies(int[] attributeSdkUsageList) { - final List audioAttrList = new ArrayList<>(attributeSdkUsageList.length); - for (int attributeSdkUsage : attributeSdkUsageList) { - audioAttrList.add(new AudioAttributes.Builder().setUsage(attributeSdkUsage).build()); - } - - final List allStrategies = getAudioProductStrategies(); - final List 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()); - } - - @VisibleForTesting - List getAudioProductStrategies() { - return AudioManager.getAudioProductStrategies(); - } - - @VisibleForTesting - boolean setPreferredDeviceForStrategies(List strategies, - AudioDeviceAttributes audioDevice) { - boolean status = true; - for (AudioProductStrategy strategy : strategies) { - status &= mAudioManager.setPreferredDeviceForStrategy(strategy, audioDevice); - } - - return status; - } - - @VisibleForTesting - boolean removePreferredDeviceForStrategies(List strategies) { - boolean status = true; - for (AudioProductStrategy strategy : strategies) { - status &= mAudioManager.removePreferredDeviceForStrategy(strategy); - } - - return status; - } - - @Retention(RetentionPolicy.SOURCE) - @IntDef({ - RoutingValue.AUTO, - RoutingValue.HEARING_DEVICE, - RoutingValue.DEVICE_SPEAKER, - }) - - @VisibleForTesting - protected @interface RoutingValue { - int AUTO = 0; - int HEARING_DEVICE = 1; - int DEVICE_SPEAKER = 2; - } } diff --git a/src/com/android/settings/bluetooth/HearingDeviceCallRoutingPreferenceController.java b/src/com/android/settings/bluetooth/HearingDeviceCallRoutingPreferenceController.java index 8daa738955f..ea5ba8bc556 100644 --- a/src/com/android/settings/bluetooth/HearingDeviceCallRoutingPreferenceController.java +++ b/src/com/android/settings/bluetooth/HearingDeviceCallRoutingPreferenceController.java @@ -17,10 +17,10 @@ package com.android.settings.bluetooth; import android.content.Context; -import android.media.AudioAttributes; import android.provider.Settings; import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.HearingAidAudioRoutingConstants; /** * The controller of the hearing device call routing list preference. @@ -45,9 +45,7 @@ public class HearingDeviceCallRoutingPreferenceController extends @Override protected int[] getSupportedAttributeList() { - return new int[]{ - AudioAttributes.USAGE_VOICE_COMMUNICATION, - AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING}; + return HearingAidAudioRoutingConstants.CALL_ROUTING_ATTRIBUTES; } @Override @@ -64,6 +62,7 @@ public class HearingDeviceCallRoutingPreferenceController extends @Override protected int restoreRoutingValue(Context context) { return Settings.Secure.getInt(context.getContentResolver(), - Settings.Secure.HEARING_AID_CALL_ROUTING, RoutingValue.AUTO); + Settings.Secure.HEARING_AID_CALL_ROUTING, + HearingAidAudioRoutingConstants.RoutingValue.AUTO); } } diff --git a/src/com/android/settings/bluetooth/HearingDeviceMediaRoutingPreferenceController.java b/src/com/android/settings/bluetooth/HearingDeviceMediaRoutingPreferenceController.java index 4e613463ff7..1ea36c783e2 100644 --- a/src/com/android/settings/bluetooth/HearingDeviceMediaRoutingPreferenceController.java +++ b/src/com/android/settings/bluetooth/HearingDeviceMediaRoutingPreferenceController.java @@ -17,10 +17,10 @@ package com.android.settings.bluetooth; import android.content.Context; -import android.media.AudioAttributes; import android.provider.Settings; import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.HearingAidAudioRoutingConstants; /** * The controller of the hearing device media routing list preference. @@ -45,9 +45,8 @@ public class HearingDeviceMediaRoutingPreferenceController extends @Override protected int[] getSupportedAttributeList() { - return new int[]{ - AudioAttributes.USAGE_MEDIA, - AudioAttributes.USAGE_GAME}; + return HearingAidAudioRoutingConstants.MEDIA_ROUTING_ATTRIBUTES; + } @Override @@ -64,6 +63,7 @@ public class HearingDeviceMediaRoutingPreferenceController extends @Override protected int restoreRoutingValue(Context context) { return Settings.Secure.getInt(context.getContentResolver(), - Settings.Secure.HEARING_AID_MEDIA_ROUTING, RoutingValue.AUTO); + Settings.Secure.HEARING_AID_MEDIA_ROUTING, + HearingAidAudioRoutingConstants.RoutingValue.AUTO); } } diff --git a/src/com/android/settings/bluetooth/HearingDeviceRingtoneRoutingPreferenceController.java b/src/com/android/settings/bluetooth/HearingDeviceRingtoneRoutingPreferenceController.java index 325d3943076..006cb6b217a 100644 --- a/src/com/android/settings/bluetooth/HearingDeviceRingtoneRoutingPreferenceController.java +++ b/src/com/android/settings/bluetooth/HearingDeviceRingtoneRoutingPreferenceController.java @@ -17,10 +17,10 @@ package com.android.settings.bluetooth; import android.content.Context; -import android.media.AudioAttributes; import android.provider.Settings; import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.HearingAidAudioRoutingConstants; /** * The controller of the hearing device ringtone routing list preference. @@ -45,7 +45,8 @@ public class HearingDeviceRingtoneRoutingPreferenceController extends @Override protected int[] getSupportedAttributeList() { - return new int[] {AudioAttributes.USAGE_NOTIFICATION_RINGTONE}; + return HearingAidAudioRoutingConstants.RINGTONE_ROUTING_ATTRIBUTE; + } @Override @@ -62,6 +63,7 @@ public class HearingDeviceRingtoneRoutingPreferenceController extends @Override protected int restoreRoutingValue(Context context) { return Settings.Secure.getInt(context.getContentResolver(), - Settings.Secure.HEARING_AID_RINGTONE_ROUTING, RoutingValue.AUTO); + Settings.Secure.HEARING_AID_RINGTONE_ROUTING, + HearingAidAudioRoutingConstants.RoutingValue.AUTO); } } diff --git a/src/com/android/settings/bluetooth/HearingDeviceSystemSoundsRoutingPreferenceController.java b/src/com/android/settings/bluetooth/HearingDeviceSystemSoundsRoutingPreferenceController.java index 19de713262e..a66cfab93a0 100644 --- a/src/com/android/settings/bluetooth/HearingDeviceSystemSoundsRoutingPreferenceController.java +++ b/src/com/android/settings/bluetooth/HearingDeviceSystemSoundsRoutingPreferenceController.java @@ -17,10 +17,10 @@ package com.android.settings.bluetooth; import android.content.Context; -import android.media.AudioAttributes; import android.provider.Settings; import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.HearingAidAudioRoutingConstants; /** * The controller of the hearing device system sounds routing list preference. @@ -46,14 +46,8 @@ public class HearingDeviceSystemSoundsRoutingPreferenceController extends @Override protected int[] getSupportedAttributeList() { - return new int[]{ - AudioAttributes.USAGE_ALARM, - AudioAttributes.USAGE_NOTIFICATION, - AudioAttributes.USAGE_NOTIFICATION_EVENT, - AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY, - AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE, - AudioAttributes.USAGE_ASSISTANCE_SONIFICATION, - AudioAttributes.USAGE_ASSISTANT}; + return HearingAidAudioRoutingConstants.SYSTEM_SOUNDS_ROUTING_ATTRIBUTES; + } @Override @@ -71,6 +65,7 @@ public class HearingDeviceSystemSoundsRoutingPreferenceController extends @Override protected int restoreRoutingValue(Context context) { return Settings.Secure.getInt(context.getContentResolver(), - Settings.Secure.HEARING_AID_SYSTEM_SOUNDS_ROUTING, RoutingValue.AUTO); + Settings.Secure.HEARING_AID_SYSTEM_SOUNDS_ROUTING, + HearingAidAudioRoutingConstants.RoutingValue.AUTO); } } diff --git a/tests/robotests/src/com/android/settings/bluetooth/HearingDeviceAudioRoutingBasePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/HearingDeviceAudioRoutingBasePreferenceControllerTest.java index 5be36c80efa..105da6546ad 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/HearingDeviceAudioRoutingBasePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/HearingDeviceAudioRoutingBasePreferenceControllerTest.java @@ -20,15 +20,13 @@ 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.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.spy; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.bluetooth.BluetoothDevice; import android.content.Context; -import android.content.SharedPreferences; import android.media.AudioAttributes; import android.media.AudioDeviceAttributes; import android.media.AudioDeviceInfo; @@ -38,7 +36,10 @@ import android.media.audiopolicy.AudioProductStrategy; import androidx.preference.ListPreference; import androidx.test.core.app.ApplicationProvider; +import com.android.settings.R; import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.HearingAidAudioRoutingConstants; +import com.android.settingslib.bluetooth.HearingAidAudioRoutingHelper; import org.junit.Before; import org.junit.Rule; @@ -63,71 +64,67 @@ public class HearingDeviceAudioRoutingBasePreferenceControllerTest { private final Context mContext = ApplicationProvider.getApplicationContext(); private static final String TEST_DEVICE_ADDRESS = "00:A1:A1:A1:A1:A1"; private static final String FAKE_KEY = "fake_key"; - private static final String TEST_SHARED_PREFERENCE = "test_bluetooth_settings"; + @Mock + private AudioProductStrategy mAudioProductStrategyMedia; @Mock private CachedBluetoothDevice mCachedBluetoothDevice; @Mock - private AudioProductStrategy mAudioProductStrategyMedia; - private AudioDeviceAttributes mHearingDeviceAttribute; - private ListPreference mListPreference; + private BluetoothDevice mBluetoothDevice; + @Spy + private HearingAidAudioRoutingHelper mHelper = new HearingAidAudioRoutingHelper(mContext); + private final ListPreference mListPreference = new ListPreference(mContext); private TestHearingDeviceAudioRoutingBasePreferenceController mController; @Before public void setUp() { - mListPreference = new ListPreference(mContext); - when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS); - mHearingDeviceAttribute = new AudioDeviceAttributes( + final AudioDeviceAttributes hearingDeviceAttribute = new AudioDeviceAttributes( AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_HEARING_AID, TEST_DEVICE_ADDRESS); + + when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); + when(mBluetoothDevice.getAnonymizedAddress()).thenReturn(TEST_DEVICE_ADDRESS); + when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS); + when(mHelper.getMatchedHearingDeviceAttributes(any())).thenReturn(hearingDeviceAttribute); when(mAudioProductStrategyMedia.getAudioAttributesForLegacyStreamType( AudioManager.STREAM_MUSIC)) .thenReturn((new AudioAttributes.Builder()).build()); - doReturn(getSharedPreferences()).when(mContext).getSharedPreferences(anyString(), anyInt()); + when(mHelper.getAudioProductStrategies()).thenReturn(List.of(mAudioProductStrategyMedia)); - mController = spy( - new TestHearingDeviceAudioRoutingBasePreferenceController(mContext, FAKE_KEY)); - mController.setupForTesting(mCachedBluetoothDevice); - doReturn(List.of(mAudioProductStrategyMedia)).when(mController).getAudioProductStrategies(); + mController = new TestHearingDeviceAudioRoutingBasePreferenceController(mContext, FAKE_KEY, + mHelper); + TestHearingDeviceAudioRoutingBasePreferenceController.setupForTesting( + mCachedBluetoothDevice); + mListPreference.setEntries(R.array.bluetooth_audio_routing_titles); + mListPreference.setEntryValues(R.array.bluetooth_audio_routing_values); + mListPreference.setSummary("%s"); } @Test - public void onPreferenceChange_routingValueAuto_expectedListValue() { - mController.onPreferenceChange(mListPreference, String.valueOf( - HearingDeviceAudioRoutingBasePreferenceController.RoutingValue.AUTO)); + public void updateState_routingValueAuto_expectedSummary() { + mController.saveRoutingValue(mContext, HearingAidAudioRoutingConstants.RoutingValue.AUTO); - verify(mController).removePreferredDeviceForStrategies(any()); - assertThat(mListPreference.getValue()).isEqualTo(String.valueOf( - HearingDeviceAudioRoutingBasePreferenceController.RoutingValue.AUTO)); + mController.updateState(mListPreference); + + assertThat(mListPreference.getSummary().toString()).isEqualTo( + mListPreference.getEntries()[0].toString()); } @Test - public void onPreferenceChange_routingValueHearingDevice_expectedListValue() { + public void onPreferenceChange_routingValueHearingDevice_restoreSameValue() { mController.onPreferenceChange(mListPreference, String.valueOf( - HearingDeviceAudioRoutingBasePreferenceController.RoutingValue.HEARING_DEVICE)); + HearingAidAudioRoutingConstants.RoutingValue.HEARING_DEVICE)); - verify(mController).setPreferredDeviceForStrategies(any(), eq(mHearingDeviceAttribute)); - assertThat(mListPreference.getValue()).isEqualTo(String.valueOf( - HearingDeviceAudioRoutingBasePreferenceController.RoutingValue.HEARING_DEVICE)); + assertThat(mController.restoreRoutingValue(mContext)).isEqualTo( + HearingAidAudioRoutingConstants.RoutingValue.HEARING_DEVICE); } @Test - public void onPreferenceChange_routingValueDeviceSpeaker_expectedListValue() { - final AudioDeviceAttributes deviceSpeakerOut = new AudioDeviceAttributes( - AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, ""); + public void onPreferenceChange_noMatchedDeviceAttributes_notCallSetStrategies() { + when(mHelper.getMatchedHearingDeviceAttributes(any())).thenReturn(null); - mController.onPreferenceChange(mListPreference, String.valueOf( - HearingDeviceAudioRoutingBasePreferenceController.RoutingValue.DEVICE_SPEAKER)); - - verify(mController).setPreferredDeviceForStrategies(any(), eq(deviceSpeakerOut)); - assertThat(mListPreference.getValue()).isEqualTo(String.valueOf( - HearingDeviceAudioRoutingBasePreferenceController.RoutingValue.DEVICE_SPEAKER)); - - } - - private SharedPreferences getSharedPreferences() { - return mContext.getSharedPreferences(TEST_SHARED_PREFERENCE, Context.MODE_PRIVATE); + verify(mHelper, never()).setPreferredDeviceRoutingStrategies(any(), isNull(), anyInt()); } private static class TestHearingDeviceAudioRoutingBasePreferenceController extends @@ -137,8 +134,8 @@ public class HearingDeviceAudioRoutingBasePreferenceControllerTest { private static int sSavedRoutingValue; TestHearingDeviceAudioRoutingBasePreferenceController(Context context, - String preferenceKey) { - super(context, preferenceKey); + String preferenceKey, HearingAidAudioRoutingHelper helper) { + super(context, preferenceKey, helper); } @Override