[HA Input] Add UI to support hearing device microphone change ability in device details page
In this patch: * Add custom dialog preference: HearingDeviceInputRoutingPreference and its controller * Contain radio group to show 'hearing device microphone' and 'this phone's microphone' for user's preference * set/get user's preference via BluetoothDevice#isMicrophonePreferredForCalls, BluetoothDevicwe#setMicrophonePreferredForCalls * check if support HapProfile and if in AudioManager#getDevice(GET_DEVICES_INPUTS) list Bug: 349255906 Test: atest HearingDeviceInputRoutingPreferenceTest BluetoothDetailsHearingDeviceInputRoutingControllerTest BluetoothDetailsHearingDeviceControllerTest Flag: com.android.settingslib.flags.hearing_devices_input_routing_control Change-Id: I2e4dbc7fb98353ed52d0d175df4e8725df6b9a05
This commit is contained in:
51
res/layout/hearing_device_input_routing_dialog.xml
Normal file
51
res/layout/hearing_device_input_routing_dialog.xml
Normal file
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
Copyright (C) 2024 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.
|
||||
-->
|
||||
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="?android:attr/dialogPreferredPadding"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textDirection="locale"
|
||||
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
|
||||
android:text="@string/bluetooth_hearing_device_input_routing_dialog_summary" />
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/input_routing_group"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:checkedButton="@id/input_from_hearing_device">
|
||||
<RadioButton
|
||||
android:id="@+id/input_from_hearing_device"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
android:text="@string/bluetooth_hearing_device_input_routing_hearing_device_option"/>
|
||||
<RadioButton
|
||||
android:id="@+id/input_from_builtin_mic"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
android:text="@string/bluetooth_hearing_device_input_routing_builtin_option"/>
|
||||
</RadioGroup>
|
||||
|
||||
</LinearLayout>
|
@@ -180,6 +180,16 @@
|
||||
<string name="bluetooth_ambient_volume_unmute">Unmute surroundings</string>
|
||||
<!-- Message when changing ambient state failed. [CHAR LIMIT=NONE] -->
|
||||
<string name="bluetooth_ambient_volume_error">Couldn\u2019t update surroundings</string>
|
||||
<!-- Connected devices settings. Title for hearing device input routing control preference. -->
|
||||
<string name="bluetooth_hearing_device_input_routing_title">Default microphone for calls</string>
|
||||
<!-- Connected devices settings. Title for hearing device input routing control dialog. -->
|
||||
<string name="bluetooth_hearing_device_input_routing_dialog_title">Default microphone</string>
|
||||
<!-- Connected devices settings. Summary for hearing device input routing control dialog. -->
|
||||
<string name="bluetooth_hearing_device_input_routing_dialog_summary">Choose a microphone for calls.</string>
|
||||
<!-- Connected devices settings. One of the input routing option. It will change input routing to hearing aid microphone. -->
|
||||
<string name="bluetooth_hearing_device_input_routing_hearing_device_option">Hearing aid microphone</string>
|
||||
<!-- Connected devices settings. One of the input routing option. It will change input routing to this phone's builtin microphone -->
|
||||
<string name="bluetooth_hearing_device_input_routing_builtin_option">This phone\'s microphone</string>
|
||||
<!-- Connected devices settings. Title of the preference to show the entrance of the audio output page. It can change different types of audio are played on phone or other bluetooth devices. [CHAR LIMIT=35] -->
|
||||
<string name="bluetooth_audio_routing_title">Audio output</string>
|
||||
<!-- Title for bluetooth audio routing page footer. [CHAR LIMIT=30] -->
|
||||
|
@@ -101,7 +101,7 @@ public abstract class HearingDeviceAudioRoutingBasePreferenceController extends
|
||||
final List<AudioProductStrategy> supportedStrategies =
|
||||
mAudioRoutingHelper.getSupportedStrategies(audioAttributes);
|
||||
final AudioDeviceAttributes hearingDeviceAttributes =
|
||||
mAudioRoutingHelper.getMatchedHearingDeviceAttributes(hearingDevice);
|
||||
mAudioRoutingHelper.getMatchedHearingDeviceAttributesForOutput(hearingDevice);
|
||||
if (hearingDeviceAttributes == null) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG,
|
||||
|
@@ -42,6 +42,7 @@ public class BluetoothDetailsHearingDeviceController extends BluetoothDetailsCon
|
||||
|
||||
public static final int ORDER_HEARING_DEVICE_SETTINGS = 1;
|
||||
public static final int ORDER_HEARING_AIDS_PRESETS = 2;
|
||||
public static final int ORDER_HEARING_DEVICE_INPUT_ROUTING = 3;
|
||||
public static final int ORDER_AMBIENT_VOLUME = 4;
|
||||
static final String KEY_HEARING_DEVICE_GROUP = "hearing_device_group";
|
||||
|
||||
@@ -62,10 +63,12 @@ public class BluetoothDetailsHearingDeviceController extends BluetoothDetailsCon
|
||||
@VisibleForTesting
|
||||
void setSubControllers(
|
||||
BluetoothDetailsHearingDeviceSettingsController hearingDeviceSettingsController,
|
||||
BluetoothDetailsHearingAidsPresetsController presetsController) {
|
||||
BluetoothDetailsHearingAidsPresetsController presetsController,
|
||||
BluetoothDetailsHearingDeviceInputRoutingController inputRoutingController) {
|
||||
mControllers.clear();
|
||||
mControllers.add(hearingDeviceSettingsController);
|
||||
mControllers.add(presetsController);
|
||||
mControllers.add(inputRoutingController);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -112,6 +115,11 @@ public class BluetoothDetailsHearingDeviceController extends BluetoothDetailsCon
|
||||
mControllers.add(new BluetoothDetailsAmbientVolumePreferenceController(mContext,
|
||||
mManager, mFragment, mCachedDevice, mLifecycle));
|
||||
}
|
||||
if (com.android.settingslib.flags.Flags.hearingDevicesInputRoutingControl()) {
|
||||
mControllers.add(
|
||||
new BluetoothDetailsHearingDeviceInputRoutingController(mContext, mFragment,
|
||||
mCachedDevice, mLifecycle));
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
|
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.android.settings.bluetooth.BluetoothDetailsHearingDeviceController.KEY_HEARING_DEVICE_GROUP;
|
||||
import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceController.ORDER_HEARING_DEVICE_INPUT_ROUTING;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.preference.PreferenceScreen;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.bluetooth.HearingDeviceInputRoutingPreference.InputRoutingValue;
|
||||
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
|
||||
import com.android.settingslib.bluetooth.HapClientProfile;
|
||||
import com.android.settingslib.bluetooth.HearingAidAudioRoutingConstants;
|
||||
import com.android.settingslib.bluetooth.HearingAidAudioRoutingHelper;
|
||||
import com.android.settingslib.core.lifecycle.Lifecycle;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* The controller of the hearing device input routing
|
||||
*
|
||||
* <p> It manages the input routing preference and update the routing according to the value.
|
||||
*/
|
||||
public class BluetoothDetailsHearingDeviceInputRoutingController extends
|
||||
BluetoothDetailsController implements
|
||||
HearingDeviceInputRoutingPreference.InputRoutingCallback {
|
||||
|
||||
private static final String TAG = "BluetoothDetailsHearingDeviceInputRoutingController";
|
||||
static final String KEY_HEARING_DEVICE_INPUT_ROUTING = "hearing_device_input_routing";
|
||||
|
||||
private final HearingAidAudioRoutingHelper mAudioRoutingHelper;
|
||||
private final AudioManager mAudioManager;
|
||||
|
||||
public BluetoothDetailsHearingDeviceInputRoutingController(
|
||||
@NonNull Context context,
|
||||
@NonNull PreferenceFragmentCompat fragment,
|
||||
@NonNull CachedBluetoothDevice device,
|
||||
@NonNull Lifecycle lifecycle) {
|
||||
super(context, fragment, device, lifecycle);
|
||||
mAudioRoutingHelper = new HearingAidAudioRoutingHelper(context);
|
||||
mAudioManager = mContext.getSystemService(AudioManager.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
boolean isSupportedProfile = mCachedDevice.getProfiles().stream().anyMatch(
|
||||
profile -> profile instanceof HapClientProfile);
|
||||
boolean isSupportedInputDevice = Arrays.stream(
|
||||
mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).anyMatch(
|
||||
info -> mCachedDevice.getAddress().equals(info.getAddress()));
|
||||
if (isSupportedProfile && !isSupportedInputDevice) {
|
||||
Log.d(TAG, "Not supported input type hearing device.");
|
||||
}
|
||||
return isSupportedProfile && isSupportedInputDevice;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init(PreferenceScreen screen) {
|
||||
PreferenceCategory hearingCategory = screen.findPreference(KEY_HEARING_DEVICE_GROUP);
|
||||
if (hearingCategory != null) {
|
||||
hearingCategory.addPreference(
|
||||
createInputRoutingPreference(hearingCategory.getContext()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void refresh() {}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getPreferenceKey() {
|
||||
return KEY_HEARING_DEVICE_INPUT_ROUTING;
|
||||
}
|
||||
|
||||
private HearingDeviceInputRoutingPreference createInputRoutingPreference(Context context) {
|
||||
HearingDeviceInputRoutingPreference pref = new HearingDeviceInputRoutingPreference(context);
|
||||
pref.setKey(KEY_HEARING_DEVICE_INPUT_ROUTING);
|
||||
pref.setOrder(ORDER_HEARING_DEVICE_INPUT_ROUTING);
|
||||
pref.setTitle(context.getString(R.string.bluetooth_hearing_device_input_routing_title));
|
||||
pref.setChecked(getUserPreferredInputRoutingValue());
|
||||
pref.setInputRoutingCallback(this);
|
||||
return pref;
|
||||
}
|
||||
|
||||
@InputRoutingValue
|
||||
private int getUserPreferredInputRoutingValue() {
|
||||
return mCachedDevice.getDevice().isMicrophonePreferredForCalls()
|
||||
? InputRoutingValue.HEARING_DEVICE : InputRoutingValue.BUILTIN_MIC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInputRoutingUpdated(int selectedInputRoutingUiValue) {
|
||||
boolean useBuiltinMic =
|
||||
(selectedInputRoutingUiValue == InputRoutingValue.BUILTIN_MIC);
|
||||
boolean status = mAudioRoutingHelper.setPreferredInputDeviceForCalls(mCachedDevice,
|
||||
useBuiltinMic ? HearingAidAudioRoutingConstants.RoutingValue.BUILTIN_DEVICE
|
||||
: HearingAidAudioRoutingConstants.RoutingValue.AUTO);
|
||||
if (!status) {
|
||||
Log.d(TAG, "Fail to configure setPreferredInputDeviceForCalls");
|
||||
}
|
||||
mCachedDevice.getDevice().setMicrophonePreferredForCalls(!useBuiltinMic);
|
||||
}
|
||||
}
|
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.content.DialogInterface;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.RadioGroup;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settingslib.CustomDialogPreferenceCompat;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Preference for controlling the input routing for hearing device.
|
||||
*
|
||||
* <p> This preference displays a dialog that allows users to choose which input device that want to
|
||||
* use when using this hearing device.
|
||||
*/
|
||||
public class HearingDeviceInputRoutingPreference extends CustomDialogPreferenceCompat {
|
||||
|
||||
/**
|
||||
* Annotations for possible input routing UI for this hearing device input routing preference.
|
||||
*/
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({
|
||||
InputRoutingValue.HEARING_DEVICE,
|
||||
InputRoutingValue.BUILTIN_MIC
|
||||
})
|
||||
public @interface InputRoutingValue {
|
||||
int HEARING_DEVICE = 0;
|
||||
int BUILTIN_MIC = 1;
|
||||
}
|
||||
|
||||
private static final int INVALID_ID = -1;
|
||||
private final Context mContext;
|
||||
private final int mFromHearingDeviceButtonId = R.id.input_from_hearing_device;
|
||||
private final int mFromBuiltinMicButtonId = R.id.input_from_builtin_mic;
|
||||
|
||||
@Nullable
|
||||
private RadioGroup mInputRoutingGroup;
|
||||
@Nullable
|
||||
private InputRoutingCallback mCallback;
|
||||
// Default value is hearing device as input
|
||||
@InputRoutingValue
|
||||
private int mSelectedInputRoutingValue = InputRoutingValue.HEARING_DEVICE;
|
||||
|
||||
|
||||
public HearingDeviceInputRoutingPreference(@NonNull Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public HearingDeviceInputRoutingPreference(@NonNull Context context,
|
||||
@Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
|
||||
mContext = context;
|
||||
setDialogTitle(R.string.bluetooth_hearing_device_input_routing_dialog_title);
|
||||
setDialogLayoutResource(R.layout.hearing_device_input_routing_dialog);
|
||||
setNegativeButtonText(R.string.cancel);
|
||||
setPositiveButtonText(R.string.done_button);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the callback to receive input routing updates.
|
||||
*/
|
||||
public void setInputRoutingCallback(@NonNull InputRoutingCallback callback) {
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link InputRoutingValue} value to determine which radio button should be checked,
|
||||
* and also update summary accordingly.
|
||||
*
|
||||
* @param inputRoutingValue The input routing value.
|
||||
*/
|
||||
public void setChecked(@InputRoutingValue int inputRoutingValue) {
|
||||
mSelectedInputRoutingValue = inputRoutingValue;
|
||||
setSummary(getSummary());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onClick(DialogInterface dialog, int which) {
|
||||
if (which == DialogInterface.BUTTON_POSITIVE) {
|
||||
int prevBtnId = getRadioButtonId(mSelectedInputRoutingValue);
|
||||
int curBtnId = Objects.requireNonNull(mInputRoutingGroup).getCheckedRadioButtonId();
|
||||
if (prevBtnId == curBtnId) {
|
||||
return;
|
||||
}
|
||||
|
||||
setChecked(getSelectedInputRoutingValue());
|
||||
if (mCallback != null) {
|
||||
mCallback.onInputRoutingUpdated(mSelectedInputRoutingValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBindDialogView(View view) {
|
||||
super.onBindDialogView(view);
|
||||
|
||||
mInputRoutingGroup = view.requireViewById(R.id.input_routing_group);
|
||||
mInputRoutingGroup.check(getRadioButtonId(mSelectedInputRoutingValue));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public CharSequence getSummary() {
|
||||
return switch (mSelectedInputRoutingValue) {
|
||||
case InputRoutingValue.HEARING_DEVICE -> mContext.getResources().getString(
|
||||
R.string.bluetooth_hearing_device_input_routing_hearing_device_option);
|
||||
case InputRoutingValue.BUILTIN_MIC -> mContext.getResources().getString(
|
||||
R.string.bluetooth_hearing_device_input_routing_builtin_option);
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
private int getRadioButtonId(@InputRoutingValue int inputRoutingValue) {
|
||||
return switch (inputRoutingValue) {
|
||||
case InputRoutingValue.HEARING_DEVICE -> mFromHearingDeviceButtonId;
|
||||
case InputRoutingValue.BUILTIN_MIC -> mFromBuiltinMicButtonId;
|
||||
default -> INVALID_ID;
|
||||
};
|
||||
}
|
||||
|
||||
@InputRoutingValue
|
||||
private int getSelectedInputRoutingValue() {
|
||||
int checkedId = Objects.requireNonNull(mInputRoutingGroup).getCheckedRadioButtonId();
|
||||
if (checkedId == mFromBuiltinMicButtonId) {
|
||||
return InputRoutingValue.BUILTIN_MIC;
|
||||
} else {
|
||||
// Should always return default value hearing device as input if something error
|
||||
// happens.
|
||||
return InputRoutingValue.HEARING_DEVICE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback to be invoked when input routing changes.
|
||||
*/
|
||||
public interface InputRoutingCallback {
|
||||
|
||||
/**
|
||||
* Called when the positive button is clicked and input routing is changed.
|
||||
*
|
||||
* @param selectedInputRoutingValue The selected input routing value.
|
||||
*/
|
||||
void onInputRoutingUpdated(@InputRoutingValue int selectedInputRoutingValue);
|
||||
}
|
||||
}
|
@@ -108,7 +108,7 @@ public class HearingDeviceAudioRoutingBasePreferenceControllerTest {
|
||||
when(mBluetoothDevice.getAnonymizedAddress()).thenReturn(TEST_DEVICE_ADDRESS);
|
||||
when(mCachedBluetoothDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
|
||||
doReturn(hearingDeviceAttribute).when(
|
||||
mAudioRoutingHelper).getMatchedHearingDeviceAttributes(any());
|
||||
mAudioRoutingHelper).getMatchedHearingDeviceAttributesForOutput(any());
|
||||
when(mAudioProductStrategyMedia.getAudioAttributesForLegacyStreamType(
|
||||
AudioManager.STREAM_MUSIC)).thenReturn((new AudioAttributes.Builder()).build());
|
||||
when(mAudioRoutingHelper.getAudioProductStrategies()).thenReturn(
|
||||
@@ -143,7 +143,8 @@ public class HearingDeviceAudioRoutingBasePreferenceControllerTest {
|
||||
|
||||
@Test
|
||||
public void onPreferenceChange_noMatchedDeviceAttributes_notCallSetStrategies() {
|
||||
when(mAudioRoutingHelper.getMatchedHearingDeviceAttributes(any())).thenReturn(null);
|
||||
when(mAudioRoutingHelper.getMatchedHearingDeviceAttributesForOutput(any())).thenReturn(
|
||||
null);
|
||||
|
||||
verify(mAudioRoutingHelper, never()).setPreferredDeviceRoutingStrategies(any(), isNull(),
|
||||
anyInt());
|
||||
|
@@ -56,6 +56,8 @@ public class BluetoothDetailsHearingDeviceControllerTest extends
|
||||
private BluetoothDetailsHearingAidsPresetsController mPresetsController;
|
||||
@Mock
|
||||
private BluetoothDetailsHearingDeviceSettingsController mHearingDeviceSettingsController;
|
||||
@Mock
|
||||
private BluetoothDetailsHearingDeviceInputRoutingController mInputRoutingController;
|
||||
|
||||
private BluetoothDetailsHearingDeviceController mHearingDeviceController;
|
||||
|
||||
@@ -67,7 +69,7 @@ public class BluetoothDetailsHearingDeviceControllerTest extends
|
||||
mHearingDeviceController = new BluetoothDetailsHearingDeviceController(mContext,
|
||||
mFragment, mLocalManager, mCachedDevice, mLifecycle);
|
||||
mHearingDeviceController.setSubControllers(mHearingDeviceSettingsController,
|
||||
mPresetsController);
|
||||
mPresetsController, mInputRoutingController);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -84,6 +86,13 @@ public class BluetoothDetailsHearingDeviceControllerTest extends
|
||||
assertThat(mHearingDeviceController.isAvailable()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAvailable_inputRoutingControllersAvailable_returnFalse() {
|
||||
when(mInputRoutingController.isAvailable()).thenReturn(true);
|
||||
|
||||
assertThat(mHearingDeviceController.isAvailable()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAvailable_noControllersAvailable_returnFalse() {
|
||||
when(mHearingDeviceSettingsController.isAvailable()).thenReturn(false);
|
||||
@@ -146,4 +155,24 @@ public class BluetoothDetailsHearingDeviceControllerTest extends
|
||||
assertThat(mHearingDeviceController.getSubControllers().stream().anyMatch(
|
||||
c -> c instanceof BluetoothDetailsAmbientVolumePreferenceController)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresFlagsEnabled(
|
||||
com.android.settingslib.flags.Flags.FLAG_HEARING_DEVICES_INPUT_ROUTING_CONTROL)
|
||||
public void initSubControllers_flagEnabled_inputRoutingControllerExist() {
|
||||
mHearingDeviceController.initSubControllers(false);
|
||||
|
||||
assertThat(mHearingDeviceController.getSubControllers().stream().anyMatch(
|
||||
c -> c instanceof BluetoothDetailsHearingDeviceInputRoutingController)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequiresFlagsDisabled(
|
||||
com.android.settingslib.flags.Flags.FLAG_HEARING_DEVICES_INPUT_ROUTING_CONTROL)
|
||||
public void initSubControllers_flagDisabled_inputRoutingControllerNotExist() {
|
||||
mHearingDeviceController.initSubControllers(false);
|
||||
|
||||
assertThat(mHearingDeviceController.getSubControllers().stream().anyMatch(
|
||||
c -> c instanceof BluetoothDetailsHearingDeviceInputRoutingController)).isFalse();
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.android.settings.bluetooth.BluetoothDetailsHearingDeviceController.KEY_HEARING_DEVICE_GROUP;
|
||||
import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceInputRoutingController.KEY_HEARING_DEVICE_INPUT_ROUTING;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.media.AudioDeviceInfo;
|
||||
import android.media.AudioManager;
|
||||
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.bluetooth.HearingDeviceInputRoutingPreference.InputRoutingValue;
|
||||
import com.android.settingslib.bluetooth.HapClientProfile;
|
||||
|
||||
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.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/** Tests for {@link BluetoothDetailsHearingDeviceInputRoutingController}. */
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class BluetoothDetailsHearingDeviceInputRoutingControllerTest extends
|
||||
BluetoothDetailsControllerTestBase {
|
||||
@Rule
|
||||
public final MockitoRule mockito = MockitoJUnit.rule();
|
||||
|
||||
private static final String TEST_ADDRESS = "55:66:77:88:99:AA";
|
||||
|
||||
@Mock
|
||||
private BluetoothDevice mBluetoothDevice;
|
||||
@Mock
|
||||
private HapClientProfile mHapClientProfile;
|
||||
@Spy
|
||||
private AudioManager mAudioManager;
|
||||
|
||||
private BluetoothDetailsHearingDeviceInputRoutingController mController;
|
||||
|
||||
@Override
|
||||
public void setUp() {
|
||||
super.setUp();
|
||||
|
||||
mContext = spy(ApplicationProvider.getApplicationContext());
|
||||
mAudioManager = spy(mContext.getSystemService(AudioManager.class));
|
||||
when(mContext.getSystemService(AudioManager.class)).thenReturn(mAudioManager);
|
||||
setupDevice(makeDefaultDeviceConfig());
|
||||
when(mCachedDevice.getDevice()).thenReturn(mBluetoothDevice);
|
||||
PreferenceCategory deviceControls = new PreferenceCategory(mContext);
|
||||
deviceControls.setKey(KEY_HEARING_DEVICE_GROUP);
|
||||
mScreen.addPreference(deviceControls);
|
||||
mController = new BluetoothDetailsHearingDeviceInputRoutingController(mContext,
|
||||
mFragment, mCachedDevice, mLifecycle);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void init_getExpectedPreference() {
|
||||
mController.init(mScreen);
|
||||
|
||||
Preference pref = mScreen.findPreference(KEY_HEARING_DEVICE_INPUT_ROUTING);
|
||||
assertThat(pref.getKey()).isEqualTo(KEY_HEARING_DEVICE_INPUT_ROUTING);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void init_setPreferredMicrophoneTrue_expectedSummary() {
|
||||
when(mBluetoothDevice.isMicrophonePreferredForCalls()).thenReturn(true);
|
||||
|
||||
mController.init(mScreen);
|
||||
|
||||
Preference pref = mScreen.findPreference(KEY_HEARING_DEVICE_INPUT_ROUTING);
|
||||
assertThat(pref.getSummary().toString()).isEqualTo(mContext.getString(
|
||||
R.string.bluetooth_hearing_device_input_routing_hearing_device_option));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void init_setPreferredMicrophoneFalse_expectedSummary() {
|
||||
when(mBluetoothDevice.isMicrophonePreferredForCalls()).thenReturn(false);
|
||||
mController.init(mScreen);
|
||||
|
||||
Preference pref = mScreen.findPreference(KEY_HEARING_DEVICE_INPUT_ROUTING);
|
||||
assertThat(pref.getSummary().toString()).isEqualTo(mContext.getString(
|
||||
R.string.bluetooth_hearing_device_input_routing_builtin_option));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onInputRoutingUpdated_hearingDevice_setMicrophonePreferredForCallsTrue() {
|
||||
mController.init(mScreen);
|
||||
|
||||
mController.onInputRoutingUpdated(InputRoutingValue.HEARING_DEVICE);
|
||||
|
||||
verify(mBluetoothDevice).setMicrophonePreferredForCalls(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onInputRoutingUpdated_builtin_setMicrophonePreferredForCallsFalse() {
|
||||
mController.init(mScreen);
|
||||
|
||||
mController.onInputRoutingUpdated(InputRoutingValue.BUILTIN_MIC);
|
||||
|
||||
verify(mBluetoothDevice).setMicrophonePreferredForCalls(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAvailable_validInput_supportHapProfile_returnTrue() {
|
||||
when(mCachedDevice.getAddress()).thenReturn(TEST_ADDRESS);
|
||||
AudioDeviceInfo[] mockInfo = new AudioDeviceInfo[] {mockTestAddressInfo(TEST_ADDRESS)};
|
||||
when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(mockInfo);
|
||||
when(mCachedDevice.getProfiles()).thenReturn(List.of(mHapClientProfile));
|
||||
|
||||
assertThat(mController.isAvailable()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAvailable_notSupportHapProfile_returnFalse() {
|
||||
when(mCachedDevice.getAddress()).thenReturn(TEST_ADDRESS);
|
||||
AudioDeviceInfo[] mockInfo = new AudioDeviceInfo[] {mockTestAddressInfo(TEST_ADDRESS)};
|
||||
when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(mockInfo);
|
||||
when(mCachedDevice.getProfiles()).thenReturn(Collections.emptyList());
|
||||
|
||||
assertThat(mController.isAvailable()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isAvailable_notValidInputDevice_returnFalse() {
|
||||
when(mCachedDevice.getAddress()).thenReturn(TEST_ADDRESS);
|
||||
when(mAudioManager.getDevices(AudioManager.GET_DEVICES_INPUTS)).thenReturn(
|
||||
new AudioDeviceInfo[] {});
|
||||
when(mCachedDevice.getProfiles()).thenReturn(List.of(mHapClientProfile));
|
||||
|
||||
assertThat(mController.isAvailable()).isFalse();
|
||||
}
|
||||
|
||||
private AudioDeviceInfo mockTestAddressInfo(String address) {
|
||||
final AudioDeviceInfo info = mock(AudioDeviceInfo.class);
|
||||
when(info.getType()).thenReturn(AudioDeviceInfo.TYPE_BLE_HEADSET);
|
||||
when(info.getAddress()).thenReturn(address);
|
||||
return info;
|
||||
}
|
||||
}
|
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright (C) 2024 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.anyInt;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.RadioGroup;
|
||||
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.bluetooth.HearingDeviceInputRoutingPreference.InputRoutingValue;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
/** Tests for {@link HearingDeviceInputRoutingPreference}. */
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class HearingDeviceInputRoutingPreferenceTest {
|
||||
@Rule
|
||||
public final MockitoRule mockito = MockitoJUnit.rule();
|
||||
|
||||
private final Context mContext = ApplicationProvider.getApplicationContext();
|
||||
private HearingDeviceInputRoutingPreference mPreference;
|
||||
private TestInputRoutingCallback mTestInputRoutingCallback;
|
||||
private View mDialogView;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
mDialogView = LayoutInflater.from(mContext).inflate(
|
||||
R.layout.hearing_device_input_routing_dialog, null);
|
||||
mTestInputRoutingCallback = spy(new TestInputRoutingCallback());
|
||||
mPreference = new HearingDeviceInputRoutingPreference(mContext);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onClick_checkToBuiltinMic_callbackWithBuiltinSpeaker() {
|
||||
mPreference.setChecked(InputRoutingValue.HEARING_DEVICE);
|
||||
mPreference.setInputRoutingCallback(mTestInputRoutingCallback);
|
||||
mPreference.onBindDialogView(mDialogView);
|
||||
RadioGroup radioGroup = mDialogView.requireViewById(R.id.input_routing_group);
|
||||
Dialog dialog = mPreference.getDialog();
|
||||
|
||||
radioGroup.check(R.id.input_from_builtin_mic);
|
||||
mPreference.onClick(dialog, DialogInterface.BUTTON_POSITIVE);
|
||||
|
||||
verify(mTestInputRoutingCallback).onInputRoutingUpdated(InputRoutingValue.BUILTIN_MIC);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setChecked_checkNoChange_noCallback() {
|
||||
mPreference.setChecked(InputRoutingValue.HEARING_DEVICE);
|
||||
mPreference.setInputRoutingCallback(mTestInputRoutingCallback);
|
||||
mPreference.onBindDialogView(mDialogView);
|
||||
Dialog dialog = mPreference.getDialog();
|
||||
|
||||
mPreference.setChecked(InputRoutingValue.HEARING_DEVICE);
|
||||
mPreference.onClick(dialog, DialogInterface.BUTTON_POSITIVE);
|
||||
|
||||
verify(mTestInputRoutingCallback, never()).onInputRoutingUpdated(anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setChecked_builtinMic_expectedSummary() {
|
||||
mPreference.setChecked(InputRoutingValue.BUILTIN_MIC);
|
||||
|
||||
assertThat(mPreference.getSummary().toString()).isEqualTo(
|
||||
mContext.getString(R.string.bluetooth_hearing_device_input_routing_builtin_option));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setChecked_hearingDevice_expectedSummary() {
|
||||
mPreference.setChecked(InputRoutingValue.HEARING_DEVICE);
|
||||
|
||||
assertThat(mPreference.getSummary().toString()).isEqualTo(mContext.getString(
|
||||
R.string.bluetooth_hearing_device_input_routing_hearing_device_option));
|
||||
}
|
||||
|
||||
private static class TestInputRoutingCallback implements
|
||||
HearingDeviceInputRoutingPreference.InputRoutingCallback {
|
||||
|
||||
@Override
|
||||
public void onInputRoutingUpdated(int selectedInputRoutingUiValue) {}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user