diff --git a/res/xml/bluetooth_audio_routing_fragment.xml b/res/xml/bluetooth_audio_routing_fragment.xml index 18f18f20740..79494c45fa5 100644 --- a/res/xml/bluetooth_audio_routing_fragment.xml +++ b/res/xml/bluetooth_audio_routing_fragment.xml @@ -30,7 +30,8 @@ android:summary="%s" android:key="audio_routing_ringtone" android:persistent="false" - android:title="@string/bluetooth_ringtone_title" /> + android:title="@string/bluetooth_ringtone_title" + settings:controller="com.android.settings.bluetooth.HearingDeviceRingtoneRoutingPreferenceController" /> + android:title="@string/bluetooth_call_title" + settings:controller="com.android.settings.bluetooth.HearingDeviceCallRoutingPreferenceController" /> + android:title="@string/bluetooth_media_title" + settings:controller="com.android.settings.bluetooth.HearingDeviceMediaRoutingPreferenceController" /> + android:title="@string/bluetooth_system_sounds_title" + settings:controller="com.android.settings.bluetooth.HearingDeviceSystemSoundsRoutingPreferenceController" /> diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingFragment.java b/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingFragment.java index 691aceeab1c..ea890538d7d 100644 --- a/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingFragment.java +++ b/src/com/android/settings/bluetooth/BluetoothDetailsAudioRoutingFragment.java @@ -65,7 +65,10 @@ public class BluetoothDetailsAudioRoutingFragment extends RestrictedDashboardFra return; } - // TODO: mCachedDevice will pass to control in next CLs. + use(HearingDeviceRingtoneRoutingPreferenceController.class).init(mCachedDevice); + use(HearingDeviceCallRoutingPreferenceController.class).init(mCachedDevice); + use(HearingDeviceMediaRoutingPreferenceController.class).init(mCachedDevice); + use(HearingDeviceSystemSoundsRoutingPreferenceController.class).init(mCachedDevice); } @Override diff --git a/src/com/android/settings/bluetooth/HearingDeviceAudioRoutingBasePreferenceController.java b/src/com/android/settings/bluetooth/HearingDeviceAudioRoutingBasePreferenceController.java new file mode 100644 index 00000000000..84412da905f --- /dev/null +++ b/src/com/android/settings/bluetooth/HearingDeviceAudioRoutingBasePreferenceController.java @@ -0,0 +1,205 @@ +/* + * 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.settings.bluetooth; + +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.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; + +/** + * Abstract class for providing audio routing {@link ListPreference} common control for hearing + * device specifically. + */ +public abstract class HearingDeviceAudioRoutingBasePreferenceController extends + BasePreferenceController implements Preference.OnPreferenceChangeListener { + + private static final String TAG = "HARoutingBasePreferenceController"; + + private static final AudioDeviceAttributes DEVICE_SPEAKER_OUT = new AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, ""); + + private final AudioManager mAudioManager; + + public HearingDeviceAudioRoutingBasePreferenceController(Context context, + String preferenceKey) { + super(context, preferenceKey); + mAudioManager = mContext.getSystemService(AudioManager.class); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public void updateState(Preference preference) { + super.updateState(preference); + + final ListPreference listPreference = (ListPreference) preference; + final int routingValue = restoreRoutingValue(mContext); + listPreference.setValue(String.valueOf(routingValue)); + } + + @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); + return true; + } + + /** + * Gets a list of usage value defined in {@link AudioAttributes} that is used to configure + * audio routing via {@link AudioProductStrategy}. + */ + protected abstract int[] getSupportedAttributeList(); + + /** + * Gets the {@link CachedBluetoothDevice} hearing device that is used to configure audio + * routing. + */ + protected abstract CachedBluetoothDevice getHearingDevice(); + + /** + * Saves the {@link RoutingValue}. + * + * @param context the valid context used to get the {@link ContentResolver} + * @param routingValue the value defined in {@link RoutingValue} + */ + protected abstract void saveRoutingValue(Context context, int routingValue); + + /** + * Restores the {@link RoutingValue} and used to reflect status on ListPreference. + * + * @param context the valid context used to get the {@link ContentResolver} + * @return one of {@link 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 new file mode 100644 index 00000000000..8daa738955f --- /dev/null +++ b/src/com/android/settings/bluetooth/HearingDeviceCallRoutingPreferenceController.java @@ -0,0 +1,69 @@ +/* + * 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.settings.bluetooth; + +import android.content.Context; +import android.media.AudioAttributes; +import android.provider.Settings; + +import com.android.settingslib.bluetooth.CachedBluetoothDevice; + +/** + * The controller of the hearing device call routing list preference. + */ +public class HearingDeviceCallRoutingPreferenceController extends + HearingDeviceAudioRoutingBasePreferenceController { + + private CachedBluetoothDevice mHearingDevice; + + public HearingDeviceCallRoutingPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + /** + * Initializes objects in this controller. Need to call this before using the controller. + * + * @param cachedBluetoothDevice the hearing device to configure audio routing + */ + public void init(CachedBluetoothDevice cachedBluetoothDevice) { + mHearingDevice = cachedBluetoothDevice; + } + + @Override + protected int[] getSupportedAttributeList() { + return new int[]{ + AudioAttributes.USAGE_VOICE_COMMUNICATION, + AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING}; + } + + @Override + protected CachedBluetoothDevice getHearingDevice() { + return mHearingDevice; + } + + @Override + protected void saveRoutingValue(Context context, int routingValue) { + Settings.Secure.putInt(context.getContentResolver(), + Settings.Secure.HEARING_AID_CALL_ROUTING, routingValue); + } + + @Override + protected int restoreRoutingValue(Context context) { + return Settings.Secure.getInt(context.getContentResolver(), + Settings.Secure.HEARING_AID_CALL_ROUTING, RoutingValue.AUTO); + } +} diff --git a/src/com/android/settings/bluetooth/HearingDeviceMediaRoutingPreferenceController.java b/src/com/android/settings/bluetooth/HearingDeviceMediaRoutingPreferenceController.java new file mode 100644 index 00000000000..4e613463ff7 --- /dev/null +++ b/src/com/android/settings/bluetooth/HearingDeviceMediaRoutingPreferenceController.java @@ -0,0 +1,69 @@ +/* + * 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.settings.bluetooth; + +import android.content.Context; +import android.media.AudioAttributes; +import android.provider.Settings; + +import com.android.settingslib.bluetooth.CachedBluetoothDevice; + +/** + * The controller of the hearing device media routing list preference. + */ +public class HearingDeviceMediaRoutingPreferenceController extends + HearingDeviceAudioRoutingBasePreferenceController { + + private CachedBluetoothDevice mHearingDevice; + + public HearingDeviceMediaRoutingPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + /** + * Initializes objects in this controller. Need to call this before using the controller. + * + * @param cachedBluetoothDevice the hearing device to configure audio routing + */ + public void init(CachedBluetoothDevice cachedBluetoothDevice) { + mHearingDevice = cachedBluetoothDevice; + } + + @Override + protected int[] getSupportedAttributeList() { + return new int[]{ + AudioAttributes.USAGE_MEDIA, + AudioAttributes.USAGE_GAME}; + } + + @Override + protected CachedBluetoothDevice getHearingDevice() { + return mHearingDevice; + } + + @Override + protected void saveRoutingValue(Context context, int routingValue) { + Settings.Secure.putInt(context.getContentResolver(), + Settings.Secure.HEARING_AID_MEDIA_ROUTING, routingValue); + } + + @Override + protected int restoreRoutingValue(Context context) { + return Settings.Secure.getInt(context.getContentResolver(), + Settings.Secure.HEARING_AID_MEDIA_ROUTING, RoutingValue.AUTO); + } +} diff --git a/src/com/android/settings/bluetooth/HearingDeviceRingtoneRoutingPreferenceController.java b/src/com/android/settings/bluetooth/HearingDeviceRingtoneRoutingPreferenceController.java new file mode 100644 index 00000000000..325d3943076 --- /dev/null +++ b/src/com/android/settings/bluetooth/HearingDeviceRingtoneRoutingPreferenceController.java @@ -0,0 +1,67 @@ +/* + * 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.settings.bluetooth; + +import android.content.Context; +import android.media.AudioAttributes; +import android.provider.Settings; + +import com.android.settingslib.bluetooth.CachedBluetoothDevice; + +/** + * The controller of the hearing device ringtone routing list preference. + */ +public class HearingDeviceRingtoneRoutingPreferenceController extends + HearingDeviceAudioRoutingBasePreferenceController { + + private CachedBluetoothDevice mHearingDevice; + + public HearingDeviceRingtoneRoutingPreferenceController(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + /** + * Initializes objects in this controller. Need to call this before using the controller. + * + * @param cachedBluetoothDevice the hearing device to configure audio routing + */ + public void init(CachedBluetoothDevice cachedBluetoothDevice) { + mHearingDevice = cachedBluetoothDevice; + } + + @Override + protected int[] getSupportedAttributeList() { + return new int[] {AudioAttributes.USAGE_NOTIFICATION_RINGTONE}; + } + + @Override + protected CachedBluetoothDevice getHearingDevice() { + return mHearingDevice; + } + + @Override + protected void saveRoutingValue(Context context, int routingValue) { + Settings.Secure.putInt(context.getContentResolver(), + Settings.Secure.HEARING_AID_RINGTONE_ROUTING, routingValue); + } + + @Override + protected int restoreRoutingValue(Context context) { + return Settings.Secure.getInt(context.getContentResolver(), + Settings.Secure.HEARING_AID_RINGTONE_ROUTING, RoutingValue.AUTO); + } +} diff --git a/src/com/android/settings/bluetooth/HearingDeviceSystemSoundsRoutingPreferenceController.java b/src/com/android/settings/bluetooth/HearingDeviceSystemSoundsRoutingPreferenceController.java new file mode 100644 index 00000000000..19de713262e --- /dev/null +++ b/src/com/android/settings/bluetooth/HearingDeviceSystemSoundsRoutingPreferenceController.java @@ -0,0 +1,76 @@ +/* + * 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.settings.bluetooth; + +import android.content.Context; +import android.media.AudioAttributes; +import android.provider.Settings; + +import com.android.settingslib.bluetooth.CachedBluetoothDevice; + +/** + * The controller of the hearing device system sounds routing list preference. + */ +public class HearingDeviceSystemSoundsRoutingPreferenceController extends + HearingDeviceAudioRoutingBasePreferenceController { + + private CachedBluetoothDevice mHearingDevice; + + public HearingDeviceSystemSoundsRoutingPreferenceController(Context context, + String preferenceKey) { + super(context, preferenceKey); + } + + /** + * Initializes objects in this controller. Need to call this before using the controller. + * + * @param cachedBluetoothDevice the hearing device to configure audio routing + */ + public void init(CachedBluetoothDevice cachedBluetoothDevice) { + mHearingDevice = cachedBluetoothDevice; + } + + @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}; + } + + @Override + protected CachedBluetoothDevice getHearingDevice() { + return mHearingDevice; + } + + @Override + protected void saveRoutingValue(Context context, int routingValue) { + Settings.Secure.putInt(context.getContentResolver(), + Settings.Secure.HEARING_AID_SYSTEM_SOUNDS_ROUTING, routingValue); + + } + + @Override + protected int restoreRoutingValue(Context context) { + return Settings.Secure.getInt(context.getContentResolver(), + Settings.Secure.HEARING_AID_SYSTEM_SOUNDS_ROUTING, RoutingValue.AUTO); + } +} diff --git a/tests/robotests/src/com/android/settings/bluetooth/HearingDeviceAudioRoutingBasePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/HearingDeviceAudioRoutingBasePreferenceControllerTest.java new file mode 100644 index 00000000000..5be36c80efa --- /dev/null +++ b/tests/robotests/src/com/android/settings/bluetooth/HearingDeviceAudioRoutingBasePreferenceControllerTest.java @@ -0,0 +1,168 @@ +/* + * 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.settings.bluetooth; + +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.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.SharedPreferences; +import android.media.AudioAttributes; +import android.media.AudioDeviceAttributes; +import android.media.AudioDeviceInfo; +import android.media.AudioManager; +import android.media.audiopolicy.AudioProductStrategy; + +import androidx.preference.ListPreference; +import androidx.test.core.app.ApplicationProvider; + +import com.android.settingslib.bluetooth.CachedBluetoothDevice; + +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.List; + +/** Tests for {@link HearingDeviceAudioRoutingBasePreferenceController}. */ +@RunWith(RobolectricTestRunner.class) +public class HearingDeviceAudioRoutingBasePreferenceControllerTest { + + @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 FAKE_KEY = "fake_key"; + private static final String TEST_SHARED_PREFERENCE = "test_bluetooth_settings"; + + @Mock + private CachedBluetoothDevice mCachedBluetoothDevice; + @Mock + private AudioProductStrategy mAudioProductStrategyMedia; + private AudioDeviceAttributes mHearingDeviceAttribute; + private ListPreference mListPreference; + private TestHearingDeviceAudioRoutingBasePreferenceController mController; + + @Before + public void setUp() { + mListPreference = new ListPreference(mContext); + when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS); + mHearingDeviceAttribute = new AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, + AudioDeviceInfo.TYPE_HEARING_AID, + TEST_DEVICE_ADDRESS); + when(mAudioProductStrategyMedia.getAudioAttributesForLegacyStreamType( + AudioManager.STREAM_MUSIC)) + .thenReturn((new AudioAttributes.Builder()).build()); + doReturn(getSharedPreferences()).when(mContext).getSharedPreferences(anyString(), anyInt()); + + mController = spy( + new TestHearingDeviceAudioRoutingBasePreferenceController(mContext, FAKE_KEY)); + mController.setupForTesting(mCachedBluetoothDevice); + doReturn(List.of(mAudioProductStrategyMedia)).when(mController).getAudioProductStrategies(); + } + + @Test + public void onPreferenceChange_routingValueAuto_expectedListValue() { + mController.onPreferenceChange(mListPreference, String.valueOf( + HearingDeviceAudioRoutingBasePreferenceController.RoutingValue.AUTO)); + + verify(mController).removePreferredDeviceForStrategies(any()); + assertThat(mListPreference.getValue()).isEqualTo(String.valueOf( + HearingDeviceAudioRoutingBasePreferenceController.RoutingValue.AUTO)); + } + + @Test + public void onPreferenceChange_routingValueHearingDevice_expectedListValue() { + mController.onPreferenceChange(mListPreference, String.valueOf( + HearingDeviceAudioRoutingBasePreferenceController.RoutingValue.HEARING_DEVICE)); + + verify(mController).setPreferredDeviceForStrategies(any(), eq(mHearingDeviceAttribute)); + assertThat(mListPreference.getValue()).isEqualTo(String.valueOf( + HearingDeviceAudioRoutingBasePreferenceController.RoutingValue.HEARING_DEVICE)); + } + + @Test + public void onPreferenceChange_routingValueDeviceSpeaker_expectedListValue() { + final AudioDeviceAttributes deviceSpeakerOut = new AudioDeviceAttributes( + AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, ""); + + 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); + } + + private static class TestHearingDeviceAudioRoutingBasePreferenceController extends + HearingDeviceAudioRoutingBasePreferenceController { + + private static CachedBluetoothDevice sCachedBluetoothDevice; + private static int sSavedRoutingValue; + + TestHearingDeviceAudioRoutingBasePreferenceController(Context context, + String preferenceKey) { + super(context, preferenceKey); + } + + @Override + protected int[] getSupportedAttributeList() { + return new int[]{AudioAttributes.USAGE_MEDIA}; + } + + @Override + protected CachedBluetoothDevice getHearingDevice() { + return sCachedBluetoothDevice; + } + + @Override + protected void saveRoutingValue(Context context, int routingValue) { + sSavedRoutingValue = routingValue; + } + + @Override + protected int restoreRoutingValue(Context context) { + return sSavedRoutingValue; + } + + public static void setupForTesting(CachedBluetoothDevice cachedBluetoothDevice) { + sCachedBluetoothDevice = cachedBluetoothDevice; + } + } +}