diff --git a/res/values/strings.xml b/res/values/strings.xml index 5ab38a48afd..3300cc61c38 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -174,6 +174,10 @@ Left Right + + Mute surroundings + + Unmute surroundings Couldn\u2019t update surroundings diff --git a/src/com/android/settings/bluetooth/AmbientVolumePreference.java b/src/com/android/settings/bluetooth/AmbientVolumePreference.java index 01222039a9b..e916c046df6 100644 --- a/src/com/android/settings/bluetooth/AmbientVolumePreference.java +++ b/src/com/android/settings/bluetooth/AmbientVolumePreference.java @@ -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 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 mSideToSliderMap = new ArrayMap<>(); + /** + * Ambient volume level for hearing device ambient control icon + *

+ * 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. + *

+ * To represent the combined left/right levels with a single value, the following calculation + * is used: + * finalLevel = (leftLevel * 5) + rightLevel + * For example: + *

+ */ + 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); + } + } } diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceController.java b/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceController.java index 887c220c99f..f237ffe50c3 100644 --- a/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceController.java +++ b/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceController.java @@ -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() diff --git a/tests/robotests/src/com/android/settings/bluetooth/AmbientVolumePreferenceTest.java b/tests/robotests/src/com/android/settings/bluetooth/AmbientVolumePreferenceTest.java index 75f3c9a1180..ec406c45503 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/AmbientVolumePreferenceTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/AmbientVolumePreferenceTest.java @@ -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 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); } diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceControllerTest.java index b7aaab4527a..975d3b491aa 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceControllerTest.java @@ -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());