[Ambient Volume] Ambient volume icon
1. Click on the icon on the header will mute/unmute the remote devices in the same set if the remote devices are both mutable. 2. Show different icon when the value of the volume slide bars changed. Flag: com.android.settingslib.flags.hearing_devices_ambient_volume_control Bug: 357878944 Test: atest AmbientVolumePreferenceTest Test: atest BluetoothDetailsAmbientVolumePreferenceControllerTest Change-Id: I829c5e08f1456c8ef9936d0314045dc8da37cd95
This commit is contained in:
@@ -174,6 +174,10 @@
|
||||
<string name="bluetooth_ambient_volume_control_left">Left</string>
|
||||
<!-- Connected devices settings. The text to show the control is for right side device. [CHAR LIMIT=30] -->
|
||||
<string name="bluetooth_ambient_volume_control_right">Right</string>
|
||||
<!-- Connected devices settings. Content description for a button, that mute ambient volume [CHAR_LIMIT=NONE] -->
|
||||
<string name="bluetooth_ambient_volume_mute">Mute surroundings</string>
|
||||
<!-- Connected devices settings. Content description for a button, that unmute ambient volume [CHAR LIMIT=NONE] -->
|
||||
<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 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] -->
|
||||
|
@@ -17,6 +17,8 @@
|
||||
package com.android.settings.bluetooth;
|
||||
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_NO;
|
||||
import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES;
|
||||
import static android.view.View.VISIBLE;
|
||||
|
||||
import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_LEFT;
|
||||
@@ -25,6 +27,7 @@ import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_R
|
||||
import android.content.Context;
|
||||
import android.util.ArrayMap;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -34,6 +37,8 @@ import androidx.preference.PreferenceViewHolder;
|
||||
import com.android.settings.R;
|
||||
import com.android.settings.widget.SeekBarPreference;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -50,10 +55,16 @@ public class AmbientVolumePreference extends PreferenceGroup {
|
||||
public interface OnIconClickListener {
|
||||
/** Called when the expand icon is clicked. */
|
||||
void onExpandIconClick();
|
||||
|
||||
/** Called when the ambient volume icon is clicked. */
|
||||
void onAmbientVolumeIconClick();
|
||||
};
|
||||
|
||||
static final float ROTATION_COLLAPSED = 0f;
|
||||
static final float ROTATION_EXPANDED = 180f;
|
||||
static final int AMBIENT_VOLUME_LEVEL_MIN = 0;
|
||||
static final int AMBIENT_VOLUME_LEVEL_MAX = 24;
|
||||
static final int AMBIENT_VOLUME_LEVEL_DEFAULT = 24;
|
||||
static final int SIDE_UNIFIED = 999;
|
||||
static final List<Integer> VALID_SIDES = List.of(SIDE_UNIFIED, SIDE_LEFT, SIDE_RIGHT);
|
||||
|
||||
@@ -61,10 +72,33 @@ public class AmbientVolumePreference extends PreferenceGroup {
|
||||
private OnIconClickListener mListener;
|
||||
@Nullable
|
||||
private View mExpandIcon;
|
||||
@Nullable
|
||||
private ImageView mVolumeIcon;
|
||||
private boolean mExpandable = true;
|
||||
private boolean mExpanded = false;
|
||||
private boolean mMutable = false;
|
||||
private boolean mMuted = false;
|
||||
private Map<Integer, SeekBarPreference> mSideToSliderMap = new ArrayMap<>();
|
||||
|
||||
/**
|
||||
* Ambient volume level for hearing device ambient control icon
|
||||
* <p>
|
||||
* This icon visually represents the current ambient gain setting.
|
||||
* It displays separate levels for the left and right sides, each with 5 levels ranging from 0
|
||||
* to 4.
|
||||
* <p>
|
||||
* To represent the combined left/right levels with a single value, the following calculation
|
||||
* is used:
|
||||
* finalLevel = (leftLevel * 5) + rightLevel
|
||||
* For example:
|
||||
* <ul>
|
||||
* <li>If left level is 2 and right level is 3, the final level will be 13 (2 * 5 + 3)</li>
|
||||
* <li>If both left and right levels are 0, the final level will be 0</li>
|
||||
* <li>If both left and right levels are 4, the final level will be 24</li>
|
||||
* </ul>
|
||||
*/
|
||||
private int mVolumeLevel = AMBIENT_VOLUME_LEVEL_DEFAULT;
|
||||
|
||||
public AmbientVolumePreference(@NonNull Context context) {
|
||||
super(context, null);
|
||||
setLayoutResource(R.layout.preference_ambient_volume);
|
||||
@@ -79,6 +113,21 @@ public class AmbientVolumePreference extends PreferenceGroup {
|
||||
holder.setDividerAllowedAbove(false);
|
||||
holder.setDividerAllowedBelow(false);
|
||||
|
||||
mVolumeIcon = holder.itemView.requireViewById(com.android.internal.R.id.icon);
|
||||
mVolumeIcon.getDrawable().mutate().setTint(getContext().getColor(
|
||||
com.android.internal.R.color.materialColorOnPrimaryContainer));
|
||||
final View iconView = holder.itemView.requireViewById(R.id.icon_frame);
|
||||
iconView.setOnClickListener(v -> {
|
||||
if (!mMutable) {
|
||||
return;
|
||||
}
|
||||
setMuted(!mMuted);
|
||||
if (mListener != null) {
|
||||
mListener.onAmbientVolumeIconClick();
|
||||
}
|
||||
});
|
||||
updateVolumeIcon();
|
||||
|
||||
mExpandIcon = holder.itemView.requireViewById(R.id.expand_icon);
|
||||
mExpandIcon.setOnClickListener(v -> {
|
||||
setExpanded(!mExpanded);
|
||||
@@ -114,6 +163,36 @@ public class AmbientVolumePreference extends PreferenceGroup {
|
||||
return mExpanded;
|
||||
}
|
||||
|
||||
void setMutable(boolean mutable) {
|
||||
mMutable = mutable;
|
||||
if (!mMutable) {
|
||||
mVolumeLevel = AMBIENT_VOLUME_LEVEL_DEFAULT;
|
||||
setMuted(false);
|
||||
}
|
||||
updateVolumeIcon();
|
||||
}
|
||||
|
||||
boolean isMutable() {
|
||||
return mMutable;
|
||||
}
|
||||
|
||||
void setMuted(boolean muted) {
|
||||
if (!mMutable && muted) {
|
||||
return;
|
||||
}
|
||||
mMuted = muted;
|
||||
if (mMutable && mMuted) {
|
||||
for (SeekBarPreference slider : mSideToSliderMap.values()) {
|
||||
slider.setProgress(slider.getMin());
|
||||
}
|
||||
}
|
||||
updateVolumeIcon();
|
||||
}
|
||||
|
||||
boolean isMuted() {
|
||||
return mMuted;
|
||||
}
|
||||
|
||||
void setOnIconClickListener(@Nullable OnIconClickListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
@@ -140,6 +219,7 @@ public class AmbientVolumePreference extends PreferenceGroup {
|
||||
SeekBarPreference slider = mSideToSliderMap.get(side);
|
||||
if (slider != null && slider.getProgress() != value) {
|
||||
slider.setProgress(value);
|
||||
updateVolumeLevel();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,6 +242,34 @@ public class AmbientVolumePreference extends PreferenceGroup {
|
||||
slider.setProgress(slider.getMin());
|
||||
}
|
||||
});
|
||||
updateVolumeLevel();
|
||||
}
|
||||
|
||||
private void updateVolumeLevel() {
|
||||
int leftLevel, rightLevel;
|
||||
if (mExpanded) {
|
||||
leftLevel = getVolumeLevel(SIDE_LEFT);
|
||||
rightLevel = getVolumeLevel(SIDE_RIGHT);
|
||||
} else {
|
||||
final int unifiedLevel = getVolumeLevel(SIDE_UNIFIED);
|
||||
leftLevel = unifiedLevel;
|
||||
rightLevel = unifiedLevel;
|
||||
}
|
||||
mVolumeLevel = Ints.constrainToRange(leftLevel * 5 + rightLevel,
|
||||
AMBIENT_VOLUME_LEVEL_MIN, AMBIENT_VOLUME_LEVEL_MAX);
|
||||
updateVolumeIcon();
|
||||
}
|
||||
|
||||
private int getVolumeLevel(int side) {
|
||||
SeekBarPreference slider = mSideToSliderMap.get(side);
|
||||
if (slider == null || !slider.isEnabled()) {
|
||||
return 0;
|
||||
}
|
||||
final double min = slider.getMin();
|
||||
final double max = slider.getMax();
|
||||
final double levelGap = (max - min) / 4.0;
|
||||
final int value = slider.getProgress();
|
||||
return (int) Math.ceil((value - min) / levelGap);
|
||||
}
|
||||
|
||||
private void updateExpandIcon() {
|
||||
@@ -179,4 +287,21 @@ public class AmbientVolumePreference extends PreferenceGroup {
|
||||
mExpandIcon.setContentDescription(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateVolumeIcon() {
|
||||
if (mVolumeIcon == null) {
|
||||
return;
|
||||
}
|
||||
mVolumeIcon.setImageLevel(mMuted ? 0 : mVolumeLevel);
|
||||
if (mMutable) {
|
||||
final int stringRes = mMuted
|
||||
? R.string.bluetooth_ambient_volume_unmute
|
||||
: R.string.bluetooth_ambient_volume_mute;
|
||||
mVolumeIcon.setContentDescription(getContext().getString(stringRes));
|
||||
mVolumeIcon.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
|
||||
} else {
|
||||
mVolumeIcon.setContentDescription(null);
|
||||
mVolumeIcon.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.settings.bluetooth;
|
||||
|
||||
import static android.bluetooth.AudioInputControl.MUTE_NOT_MUTED;
|
||||
import static android.bluetooth.AudioInputControl.MUTE_MUTED;
|
||||
import static android.bluetooth.BluetoothDevice.BOND_BONDED;
|
||||
|
||||
import static com.android.settings.bluetooth.AmbientVolumePreference.SIDE_UNIFIED;
|
||||
@@ -180,9 +182,8 @@ public class BluetoothDetailsAmbientVolumePreferenceController extends
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
boolean isDeviceSupportVcp = mCachedDevice.getProfiles().stream().anyMatch(
|
||||
return mCachedDevice.getProfiles().stream().anyMatch(
|
||||
profile -> profile instanceof VolumeControlProfile);
|
||||
return isDeviceSupportVcp;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -200,11 +201,30 @@ public class BluetoothDetailsAmbientVolumePreferenceController extends
|
||||
}
|
||||
setVolumeIfValid(side, value);
|
||||
|
||||
if (side == SIDE_UNIFIED) {
|
||||
mSideToDeviceMap.forEach((s, d) -> mVolumeController.setAmbient(d, value));
|
||||
Runnable setAmbientRunnable = () -> {
|
||||
if (side == SIDE_UNIFIED) {
|
||||
mSideToDeviceMap.forEach((s, d) -> mVolumeController.setAmbient(d, value));
|
||||
} else {
|
||||
final BluetoothDevice device = mSideToDeviceMap.get(side);
|
||||
mVolumeController.setAmbient(device, value);
|
||||
}
|
||||
};
|
||||
|
||||
if (isControlMuted()) {
|
||||
// User drag on the volume slider when muted. Unmute the devices first.
|
||||
if (mPreference != null) {
|
||||
mPreference.setMuted(false);
|
||||
}
|
||||
for (BluetoothDevice device : mSideToDeviceMap.values()) {
|
||||
mVolumeController.setMuted(device, false);
|
||||
}
|
||||
// Restore the value before muted
|
||||
loadLocalDataToUi();
|
||||
// Delay set ambient on remote device since the immediately sequential command
|
||||
// might get failed sometimes
|
||||
mContext.getMainThreadHandler().postDelayed(setAmbientRunnable, 1000L);
|
||||
} else {
|
||||
final BluetoothDevice device = mSideToDeviceMap.get(side);
|
||||
mVolumeController.setAmbient(device, value);
|
||||
setAmbientRunnable.run();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -284,6 +304,24 @@ public class BluetoothDetailsAmbientVolumePreferenceController extends
|
||||
mContext.getMainThreadHandler().postDelayed(this::refresh, 1200L);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMuteChanged(@NonNull BluetoothDevice device, int mute) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onMuteChanged, mute:" + mute + ", device:" + device);
|
||||
}
|
||||
boolean isInitiatedFromUi = (isControlMuted() && mute == MUTE_MUTED)
|
||||
|| (!isControlMuted() && mute == MUTE_NOT_MUTED);
|
||||
if (isInitiatedFromUi) {
|
||||
// The change is initiated from UI, no need to update UI
|
||||
return;
|
||||
}
|
||||
|
||||
// We have to check if we need to mute the devices by getting all remote
|
||||
// device's mute state, delay for a while to wait all remote devices update
|
||||
// to the latest value.
|
||||
mContext.getMainThreadHandler().postDelayed(this::refresh, 1200L);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCommandFailed(@NonNull BluetoothDevice device) {
|
||||
Log.w(TAG, "onCommandFailed, device:" + device);
|
||||
@@ -324,17 +362,33 @@ public class BluetoothDetailsAmbientVolumePreferenceController extends
|
||||
mPreference = new AmbientVolumePreference(mDeviceControls.getContext());
|
||||
mPreference.setKey(KEY_AMBIENT_VOLUME);
|
||||
mPreference.setOrder(ORDER_AMBIENT_VOLUME);
|
||||
mPreference.setOnIconClickListener(() -> {
|
||||
mSideToDeviceMap.forEach((s, d) -> {
|
||||
// Apply previous collapsed/expanded volume to remote device
|
||||
Data data = mLocalDataManager.get(d);
|
||||
int volume = isControlExpanded()
|
||||
? data.ambient() : data.groupAmbient();
|
||||
mVolumeController.setAmbient(d, volume);
|
||||
// Update new value to local data
|
||||
mLocalDataManager.updateAmbientControlExpanded(d, isControlExpanded());
|
||||
});
|
||||
});
|
||||
mPreference.setOnIconClickListener(
|
||||
new AmbientVolumePreference.OnIconClickListener() {
|
||||
@Override
|
||||
public void onExpandIconClick() {
|
||||
mSideToDeviceMap.forEach((s, d) -> {
|
||||
if (!isControlMuted()) {
|
||||
// Apply previous collapsed/expanded volume to remote device
|
||||
Data data = mLocalDataManager.get(d);
|
||||
int volume = isControlExpanded()
|
||||
? data.ambient() : data.groupAmbient();
|
||||
mVolumeController.setAmbient(d, volume);
|
||||
}
|
||||
// Update new value to local data
|
||||
mLocalDataManager.updateAmbientControlExpanded(d, isControlExpanded());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAmbientVolumeIconClick() {
|
||||
if (!isControlMuted()) {
|
||||
loadLocalDataToUi();
|
||||
}
|
||||
for (BluetoothDevice device : mSideToDeviceMap.values()) {
|
||||
mVolumeController.setMuted(device, isControlMuted());
|
||||
}
|
||||
}
|
||||
});
|
||||
if (mDeviceControls.findPreference(mPreference.getKey()) == null) {
|
||||
mDeviceControls.addPreference(mPreference);
|
||||
}
|
||||
@@ -406,7 +460,7 @@ public class BluetoothDetailsAmbientVolumePreferenceController extends
|
||||
Log.d(TAG, "loadLocalDataToUi, data=" + data + ", device=" + device);
|
||||
}
|
||||
final int side = mSideToDeviceMap.inverse().getOrDefault(device, SIDE_INVALID);
|
||||
if (isDeviceConnectedToVcp(device)) {
|
||||
if (isDeviceConnectedToVcp(device) && !isControlMuted()) {
|
||||
setVolumeIfValid(side, data.ambient());
|
||||
setVolumeIfValid(SIDE_UNIFIED, data.groupAmbient());
|
||||
}
|
||||
@@ -456,6 +510,26 @@ public class BluetoothDetailsAmbientVolumePreferenceController extends
|
||||
// Initialize local data between side and group value
|
||||
initLocalDataIfNeeded();
|
||||
|
||||
// Update mute state
|
||||
boolean mutable = true;
|
||||
boolean muted = true;
|
||||
if (isDeviceConnectedToVcp(leftDevice) && leftState != null) {
|
||||
mutable &= leftState.isMutable();
|
||||
muted &= leftState.isMuted();
|
||||
}
|
||||
if (isDeviceConnectedToVcp(rightDevice) && rightState != null) {
|
||||
mutable &= rightState.isMutable();
|
||||
muted &= rightState.isMuted();
|
||||
}
|
||||
if (mPreference != null) {
|
||||
mPreference.setMutable(mutable);
|
||||
mPreference.setMuted(muted);
|
||||
}
|
||||
|
||||
// Ensure remote device mute state is synced
|
||||
syncMuteStateIfNeeded(leftDevice, leftState, muted);
|
||||
syncMuteStateIfNeeded(rightDevice, rightState, muted);
|
||||
|
||||
refreshControlUi();
|
||||
}
|
||||
|
||||
@@ -488,6 +562,10 @@ public class BluetoothDetailsAmbientVolumePreferenceController extends
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isControlMuted() {
|
||||
return mPreference != null && mPreference.isMuted();
|
||||
}
|
||||
|
||||
private void initLocalDataIfNeeded() {
|
||||
int smallerVolumeAmongGroup = Integer.MAX_VALUE;
|
||||
for (BluetoothDevice device : mSideToDeviceMap.values()) {
|
||||
@@ -510,6 +588,15 @@ public class BluetoothDetailsAmbientVolumePreferenceController extends
|
||||
}
|
||||
}
|
||||
|
||||
private void syncMuteStateIfNeeded(@Nullable BluetoothDevice device,
|
||||
@Nullable AmbientVolumeController.RemoteAmbientState state, boolean muted) {
|
||||
if (isDeviceConnectedToVcp(device) && state != null && state.isMutable()) {
|
||||
if (state.isMuted() != muted) {
|
||||
mVolumeController.setMuted(device, muted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isDeviceConnectedToVcp(@Nullable BluetoothDevice device) {
|
||||
return device != null && device.isConnected()
|
||||
&& mBluetoothManager.getProfileManager().getVolumeControlProfile()
|
||||
|
@@ -57,6 +57,9 @@ import java.util.Map;
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class AmbientVolumePreferenceTest {
|
||||
|
||||
private static final int TEST_LEFT_VOLUME_LEVEL = 1;
|
||||
private static final int TEST_RIGHT_VOLUME_LEVEL = 2;
|
||||
private static final int TEST_UNIFIED_VOLUME_LEVEL = 3;
|
||||
private static final String KEY_UNIFIED_SLIDER = KEY_AMBIENT_VOLUME_SLIDER + "_" + SIDE_UNIFIED;
|
||||
private static final String KEY_LEFT_SLIDER = KEY_AMBIENT_VOLUME_SLIDER + "_" + SIDE_LEFT;
|
||||
private static final String KEY_RIGHT_SLIDER = KEY_AMBIENT_VOLUME_SLIDER + "_" + SIDE_RIGHT;
|
||||
@@ -72,6 +75,7 @@ public class AmbientVolumePreferenceTest {
|
||||
|
||||
private AmbientVolumePreference mPreference;
|
||||
private ImageView mExpandIcon;
|
||||
private ImageView mVolumeIcon;
|
||||
private final Map<Integer, SeekBarPreference> mSideToSlidersMap = new ArrayMap<>();
|
||||
|
||||
@Before
|
||||
@@ -82,13 +86,19 @@ public class AmbientVolumePreferenceTest {
|
||||
mPreference.setKey(KEY_AMBIENT_VOLUME);
|
||||
mPreference.setOnIconClickListener(mListener);
|
||||
mPreference.setExpandable(true);
|
||||
mPreference.setMutable(true);
|
||||
preferenceScreen.addPreference(mPreference);
|
||||
|
||||
prepareSliders();
|
||||
mPreference.setSliders(mSideToSlidersMap);
|
||||
|
||||
mExpandIcon = new ImageView(mContext);
|
||||
mVolumeIcon = new ImageView(mContext);
|
||||
mVolumeIcon.setImageResource(com.android.settingslib.R.drawable.ic_ambient_volume);
|
||||
mVolumeIcon.setImageLevel(0);
|
||||
when(mItemView.requireViewById(R.id.expand_icon)).thenReturn(mExpandIcon);
|
||||
when(mItemView.requireViewById(com.android.internal.R.id.icon)).thenReturn(mVolumeIcon);
|
||||
when(mItemView.requireViewById(R.id.icon_frame)).thenReturn(mVolumeIcon);
|
||||
|
||||
PreferenceViewHolder preferenceViewHolder = PreferenceViewHolder.createInstanceForTests(
|
||||
mItemView);
|
||||
@@ -123,6 +133,77 @@ public class AmbientVolumePreferenceTest {
|
||||
assertControlUiCorrect();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setMutable_mutable_clickOnMuteIconChangeMuteState() {
|
||||
mPreference.setMutable(true);
|
||||
mPreference.setMuted(false);
|
||||
|
||||
mVolumeIcon.callOnClick();
|
||||
|
||||
assertThat(mPreference.isMuted()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setMutable_notMutable_clickOnMuteIconWontChangeMuteState() {
|
||||
mPreference.setMutable(false);
|
||||
mPreference.setMuted(false);
|
||||
|
||||
mVolumeIcon.callOnClick();
|
||||
|
||||
assertThat(mPreference.isMuted()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateLayout_mute_volumeIconIsCorrect() {
|
||||
mPreference.setMuted(true);
|
||||
mPreference.updateLayout();
|
||||
|
||||
assertThat(mVolumeIcon.getDrawable().getLevel()).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateLayout_unmuteAndExpanded_volumeIconIsCorrect() {
|
||||
mPreference.setMuted(false);
|
||||
mPreference.setExpanded(true);
|
||||
mPreference.updateLayout();
|
||||
|
||||
int expectedLevel = calculateVolumeLevel(TEST_LEFT_VOLUME_LEVEL, TEST_RIGHT_VOLUME_LEVEL);
|
||||
assertThat(mVolumeIcon.getDrawable().getLevel()).isEqualTo(expectedLevel);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateLayout_unmuteAndNotExpanded_volumeIconIsCorrect() {
|
||||
mPreference.setMuted(false);
|
||||
mPreference.setExpanded(false);
|
||||
mPreference.updateLayout();
|
||||
|
||||
int expectedLevel = calculateVolumeLevel(TEST_UNIFIED_VOLUME_LEVEL,
|
||||
TEST_UNIFIED_VOLUME_LEVEL);
|
||||
assertThat(mVolumeIcon.getDrawable().getLevel()).isEqualTo(expectedLevel);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setSliderEnabled_expandedAndLeftIsDisabled_volumeIconIcCorrect() {
|
||||
mPreference.setExpanded(true);
|
||||
mPreference.setSliderEnabled(SIDE_LEFT, false);
|
||||
|
||||
int expectedLevel = calculateVolumeLevel(0, TEST_RIGHT_VOLUME_LEVEL);
|
||||
assertThat(mVolumeIcon.getDrawable().getLevel()).isEqualTo(expectedLevel);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setSliderValue_expandedAndLeftValueChanged_volumeIconIcCorrect() {
|
||||
mPreference.setExpanded(true);
|
||||
mPreference.setSliderValue(SIDE_LEFT, 4);
|
||||
|
||||
int expectedLevel = calculateVolumeLevel(4, TEST_RIGHT_VOLUME_LEVEL);
|
||||
assertThat(mVolumeIcon.getDrawable().getLevel()).isEqualTo(expectedLevel);
|
||||
}
|
||||
|
||||
private int calculateVolumeLevel(int left, int right) {
|
||||
return left * 5 + right;
|
||||
}
|
||||
|
||||
private void assertControlUiCorrect() {
|
||||
final boolean expanded = mPreference.isExpanded();
|
||||
assertThat(mSideToSlidersMap.get(SIDE_UNIFIED).isVisible()).isEqualTo(!expanded);
|
||||
@@ -140,12 +221,17 @@ public class AmbientVolumePreferenceTest {
|
||||
|
||||
private void prepareSlider(int side) {
|
||||
SeekBarPreference slider = new SeekBarPreference(mContext);
|
||||
slider.setMin(0);
|
||||
slider.setMax(4);
|
||||
if (side == SIDE_LEFT) {
|
||||
slider.setKey(KEY_LEFT_SLIDER);
|
||||
slider.setProgress(TEST_LEFT_VOLUME_LEVEL);
|
||||
} else if (side == SIDE_RIGHT) {
|
||||
slider.setKey(KEY_RIGHT_SLIDER);
|
||||
slider.setProgress(TEST_RIGHT_VOLUME_LEVEL);
|
||||
} else {
|
||||
slider.setKey(KEY_UNIFIED_SLIDER);
|
||||
slider.setProgress(TEST_UNIFIED_VOLUME_LEVEL);
|
||||
}
|
||||
mSideToSlidersMap.put(side, slider);
|
||||
}
|
||||
|
@@ -16,6 +16,9 @@
|
||||
|
||||
package com.android.settings.bluetooth;
|
||||
|
||||
import static android.bluetooth.AudioInputControl.MUTE_DISABLED;
|
||||
import static android.bluetooth.AudioInputControl.MUTE_NOT_MUTED;
|
||||
import static android.bluetooth.AudioInputControl.MUTE_MUTED;
|
||||
import static android.bluetooth.BluetoothDevice.BOND_BONDED;
|
||||
|
||||
import static com.android.settings.bluetooth.BluetoothDetailsAmbientVolumePreferenceController.KEY_AMBIENT_VOLUME;
|
||||
@@ -71,6 +74,7 @@ import org.robolectric.annotation.Implements;
|
||||
import org.robolectric.shadows.ShadowSettings;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Executor;
|
||||
@@ -135,6 +139,9 @@ public class BluetoothDetailsAmbientVolumePreferenceControllerTest extends
|
||||
BluetoothProfile.STATE_CONNECTED);
|
||||
when(mVolumeControlProfile.getConnectionStatus(mMemberDevice)).thenReturn(
|
||||
BluetoothProfile.STATE_CONNECTED);
|
||||
when(mCachedDevice.getProfiles()).thenReturn(List.of(mVolumeControlProfile));
|
||||
when(mLocalDataManager.get(any(BluetoothDevice.class))).thenReturn(
|
||||
new HearingDeviceLocalDataManager.Data.Builder().build());
|
||||
|
||||
when(mContext.getMainThreadHandler()).thenReturn(mTestHandler);
|
||||
when(mTestHandler.postDelayed(any(Runnable.class), anyLong())).thenAnswer(
|
||||
@@ -307,6 +314,68 @@ public class BluetoothDetailsAmbientVolumePreferenceControllerTest extends
|
||||
verify(mController).refresh();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onMuteChanged_refreshWhenNotInitiateFromUi() {
|
||||
prepareDevice(/* hasMember= */ false);
|
||||
mController.init(mScreen);
|
||||
final int testMute = MUTE_NOT_MUTED;
|
||||
AmbientVolumeController.RemoteAmbientState state =
|
||||
new AmbientVolumeController.RemoteAmbientState(testMute, 0);
|
||||
when(mVolumeController.refreshAmbientState(mDevice)).thenReturn(state);
|
||||
getPreference().setMuted(false);
|
||||
|
||||
mController.onMuteChanged(mDevice, testMute);
|
||||
verify(mController, never()).refresh();
|
||||
|
||||
final int updatedTestMute = MUTE_MUTED;
|
||||
mController.onMuteChanged(mDevice, updatedTestMute);
|
||||
verify(mController).refresh();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void refresh_leftAndRightDifferentGainSetting_expandControl() {
|
||||
prepareDevice(/* hasMember= */ true);
|
||||
mController.init(mScreen);
|
||||
prepareRemoteData(mDevice, 10, MUTE_NOT_MUTED);
|
||||
prepareRemoteData(mMemberDevice, 20, MUTE_NOT_MUTED);
|
||||
getPreference().setExpanded(false);
|
||||
|
||||
mController.refresh();
|
||||
|
||||
assertThat(getPreference().isExpanded()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void refresh_oneSideNotMutable_controlNotMutableAndNotMuted() {
|
||||
prepareDevice(/* hasMember= */ true);
|
||||
mController.init(mScreen);
|
||||
prepareRemoteData(mDevice, 10, MUTE_DISABLED);
|
||||
prepareRemoteData(mMemberDevice, 20, MUTE_NOT_MUTED);
|
||||
getPreference().setMutable(true);
|
||||
getPreference().setMuted(true);
|
||||
|
||||
mController.refresh();
|
||||
|
||||
assertThat(getPreference().isMutable()).isFalse();
|
||||
assertThat(getPreference().isMuted()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void refresh_oneSideNotMuted_controlNotMutedAndSyncToRemote() {
|
||||
prepareDevice(/* hasMember= */ true);
|
||||
mController.init(mScreen);
|
||||
prepareRemoteData(mDevice, 10, MUTE_MUTED);
|
||||
prepareRemoteData(mMemberDevice, 20, MUTE_NOT_MUTED);
|
||||
getPreference().setMutable(true);
|
||||
getPreference().setMuted(true);
|
||||
|
||||
mController.refresh();
|
||||
|
||||
assertThat(getPreference().isMutable()).isTrue();
|
||||
assertThat(getPreference().isMuted()).isFalse();
|
||||
verify(mVolumeController).setMuted(mDevice, false);
|
||||
}
|
||||
|
||||
private void prepareDevice(boolean hasMember) {
|
||||
when(mCachedDevice.getDeviceSide()).thenReturn(SIDE_LEFT);
|
||||
when(mCachedDevice.getDevice()).thenReturn(mDevice);
|
||||
@@ -325,6 +394,12 @@ public class BluetoothDetailsAmbientVolumePreferenceControllerTest extends
|
||||
}
|
||||
}
|
||||
|
||||
private void prepareRemoteData(BluetoothDevice device, int gainSetting, int mute) {
|
||||
when(mVolumeController.isAmbientControlAvailable(device)).thenReturn(true);
|
||||
when(mVolumeController.refreshAmbientState(device)).thenReturn(
|
||||
new AmbientVolumeController.RemoteAmbientState(gainSetting, mute));
|
||||
}
|
||||
|
||||
private void verifyDeviceDataUpdated(BluetoothDevice device) {
|
||||
verify(mLocalDataManager, atLeastOnce()).updateAmbient(eq(device), anyInt());
|
||||
verify(mLocalDataManager, atLeastOnce()).updateGroupAmbient(eq(device), anyInt());
|
||||
|
Reference in New Issue
Block a user