[Ambient Volume] Migrate to use AmbientVolumeUiController in SettingsLib
Moove the common ui logic code into settingslib for using in both
settings and systemui.
Flag: com.android.settingslib.flags.hearing_devices_ambient_volume_control
Bug: 357878944
Test: atest AmbientVolumePreferenceTest
Test: atest BluetoothDetailsAmbientVolumePreferenceControllerTest
Change-Id: I97d5ac2d1862fed7249af8b35f04fa36fc47d16d
diff --git a/src/com/android/settings/bluetooth/AmbientVolumePreference.java b/src/com/android/settings/bluetooth/AmbientVolumePreference.java
index e916c04..8196edf 100644
--- a/src/com/android/settings/bluetooth/AmbientVolumePreference.java
+++ b/src/com/android/settings/bluetooth/AmbientVolumePreference.java
@@ -21,25 +21,29 @@
import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES;
import static android.view.View.VISIBLE;
+import static com.android.settings.bluetooth.BluetoothDetailsAmbientVolumePreferenceController.KEY_AMBIENT_VOLUME_SLIDER;
import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_LEFT;
import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_RIGHT;
+import android.bluetooth.BluetoothDevice;
import android.content.Context;
-import android.util.ArrayMap;
import android.view.View;
import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceViewHolder;
import com.android.settings.R;
import com.android.settings.widget.SeekBarPreference;
+import com.android.settingslib.bluetooth.AmbientVolumeUi;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.HashBiMap;
import com.google.common.primitives.Ints;
-import java.util.List;
import java.util.Map;
/**
@@ -49,27 +53,13 @@
* separated control for devices in the same set. Toggle the expand icon will make the UI switch
* between unified and separated control.
*/
-public class AmbientVolumePreference extends PreferenceGroup {
+public class AmbientVolumePreference extends PreferenceGroup implements AmbientVolumeUi {
- /** Interface definition for a callback to be invoked when the icon is clicked. */
- 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);
+ private static final int ORDER_AMBIENT_VOLUME_CONTROL_UNIFIED = 0;
+ private static final int ORDER_AMBIENT_VOLUME_CONTROL_SEPARATED = 1;
@Nullable
- private OnIconClickListener mListener;
+ private AmbientVolumeUiListener mListener;
@Nullable
private View mExpandIcon;
@Nullable
@@ -78,27 +68,21 @@
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 final BiMap<Integer, SeekBarPreference> mSideToSliderMap = HashBiMap.create();
private int mVolumeLevel = AMBIENT_VOLUME_LEVEL_DEFAULT;
+ private final OnPreferenceChangeListener mPreferenceChangeListener =
+ (slider, v) -> {
+ if (slider instanceof SeekBarPreference && v instanceof final Integer value) {
+ final Integer side = mSideToSliderMap.inverse().get(slider);
+ if (mListener != null && side != null) {
+ mListener.onSliderValueChange(side, value);
+ }
+ return true;
+ }
+ return false;
+ };
+
public AmbientVolumePreference(@NonNull Context context) {
super(context, null);
setLayoutResource(R.layout.preference_ambient_volume);
@@ -138,7 +122,8 @@
updateExpandIcon();
}
- void setExpandable(boolean expandable) {
+ @Override
+ public void setExpandable(boolean expandable) {
mExpandable = expandable;
if (!mExpandable) {
setExpanded(false);
@@ -146,11 +131,13 @@
updateExpandIcon();
}
- boolean isExpandable() {
+ @Override
+ public boolean isExpandable() {
return mExpandable;
}
- void setExpanded(boolean expanded) {
+ @Override
+ public void setExpanded(boolean expanded) {
if (!mExpandable && expanded) {
return;
}
@@ -159,11 +146,13 @@
updateLayout();
}
- boolean isExpanded() {
+ @Override
+ public boolean isExpanded() {
return mExpanded;
}
- void setMutable(boolean mutable) {
+ @Override
+ public void setMutable(boolean mutable) {
mMutable = mutable;
if (!mMutable) {
mVolumeLevel = AMBIENT_VOLUME_LEVEL_DEFAULT;
@@ -172,11 +161,13 @@
updateVolumeIcon();
}
- boolean isMutable() {
+ @Override
+ public boolean isMutable() {
return mMutable;
}
- void setMuted(boolean muted) {
+ @Override
+ public void setMuted(boolean muted) {
if (!mMutable && muted) {
return;
}
@@ -189,25 +180,35 @@
updateVolumeIcon();
}
- boolean isMuted() {
+ @Override
+ public boolean isMuted() {
return mMuted;
}
- void setOnIconClickListener(@Nullable OnIconClickListener listener) {
+ @Override
+ public void setListener(@Nullable AmbientVolumeUiListener listener) {
mListener = listener;
}
- void setSliders(Map<Integer, SeekBarPreference> sideToSliderMap) {
- mSideToSliderMap = sideToSliderMap;
- for (SeekBarPreference preference : sideToSliderMap.values()) {
- if (findPreference(preference.getKey()) == null) {
- addPreference(preference);
+ @Override
+ public void setupSliders(@NonNull Map<Integer, BluetoothDevice> sideToDeviceMap) {
+ sideToDeviceMap.forEach((side, device) ->
+ createSlider(side, ORDER_AMBIENT_VOLUME_CONTROL_SEPARATED + side));
+ createSlider(SIDE_UNIFIED, ORDER_AMBIENT_VOLUME_CONTROL_UNIFIED);
+
+ if (!mSideToSliderMap.isEmpty()) {
+ for (int side : VALID_SIDES) {
+ final SeekBarPreference slider = mSideToSliderMap.get(side);
+ if (slider != null && findPreference(slider.getKey()) == null) {
+ addPreference(slider);
+ }
}
}
updateLayout();
}
- void setSliderEnabled(int side, boolean enabled) {
+ @Override
+ public void setSliderEnabled(int side, boolean enabled) {
SeekBarPreference slider = mSideToSliderMap.get(side);
if (slider != null && slider.isEnabled() != enabled) {
slider.setEnabled(enabled);
@@ -215,7 +216,8 @@
}
}
- void setSliderValue(int side, int value) {
+ @Override
+ public void setSliderValue(int side, int value) {
SeekBarPreference slider = mSideToSliderMap.get(side);
if (slider != null && slider.getProgress() != value) {
slider.setProgress(value);
@@ -223,7 +225,8 @@
}
}
- void setSliderRange(int side, int min, int max) {
+ @Override
+ public void setSliderRange(int side, int min, int max) {
SeekBarPreference slider = mSideToSliderMap.get(side);
if (slider != null) {
slider.setMin(min);
@@ -231,7 +234,8 @@
}
}
- void updateLayout() {
+ @Override
+ public void updateLayout() {
mSideToSliderMap.forEach((side, slider) -> {
if (side == SIDE_UNIFIED) {
slider.setVisible(!mExpanded);
@@ -279,8 +283,7 @@
mExpandIcon.setVisibility(mExpandable ? VISIBLE : GONE);
mExpandIcon.setRotation(mExpanded ? ROTATION_EXPANDED : ROTATION_COLLAPSED);
if (mExpandable) {
- final int stringRes = mExpanded
- ? R.string.bluetooth_ambient_volume_control_collapse
+ final int stringRes = mExpanded ? R.string.bluetooth_ambient_volume_control_collapse
: R.string.bluetooth_ambient_volume_control_expand;
mExpandIcon.setContentDescription(getContext().getString(stringRes));
} else {
@@ -294,8 +297,7 @@
}
mVolumeIcon.setImageLevel(mMuted ? 0 : mVolumeLevel);
if (mMutable) {
- final int stringRes = mMuted
- ? R.string.bluetooth_ambient_volume_unmute
+ 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);
@@ -304,4 +306,27 @@
mVolumeIcon.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
}
}
+
+ private void createSlider(int side, int order) {
+ if (mSideToSliderMap.containsKey(side)) {
+ return;
+ }
+ SeekBarPreference slider = new SeekBarPreference(getContext());
+ slider.setKey(KEY_AMBIENT_VOLUME_SLIDER + "_" + side);
+ slider.setOrder(order);
+ slider.setOnPreferenceChangeListener(mPreferenceChangeListener);
+ if (side == SIDE_LEFT) {
+ slider.setTitle(
+ getContext().getString(R.string.bluetooth_ambient_volume_control_left));
+ } else if (side == SIDE_RIGHT) {
+ slider.setTitle(
+ getContext().getString(R.string.bluetooth_ambient_volume_control_right));
+ }
+ mSideToSliderMap.put(side, slider);
+ }
+
+ @VisibleForTesting
+ Map<Integer, SeekBarPreference> getSliders() {
+ return mSideToSliderMap;
+ }
}
diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceController.java b/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceController.java
index f237ffe..4b0b5d4 100644
--- a/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceController.java
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceController.java
@@ -16,41 +16,20 @@
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;
-import static com.android.settings.bluetooth.AmbientVolumePreference.VALID_SIDES;
import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceController.KEY_HEARING_DEVICE_GROUP;
import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceController.ORDER_AMBIENT_VOLUME;
-import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_INVALID;
-import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_LEFT;
-import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_RIGHT;
-import static com.android.settingslib.bluetooth.HearingDeviceLocalDataManager.Data.INVALID_VOLUME;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothProfile;
import android.content.Context;
-import android.util.ArraySet;
-import android.util.Log;
-import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
-import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceScreen;
-import com.android.settings.R;
-import com.android.settings.widget.SeekBarPreference;
-import com.android.settingslib.bluetooth.AmbientVolumeController;
-import com.android.settingslib.bluetooth.BluetoothCallback;
+import com.android.settingslib.bluetooth.AmbientVolumeUiController;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.bluetooth.HearingDeviceLocalDataManager;
-import com.android.settingslib.bluetooth.HearingDeviceLocalDataManager.Data;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.VolumeControlProfile;
import com.android.settingslib.core.lifecycle.Lifecycle;
@@ -58,39 +37,21 @@
import com.android.settingslib.core.lifecycle.events.OnStop;
import com.android.settingslib.utils.ThreadUtils;
-import com.google.common.collect.BiMap;
-import com.google.common.collect.HashBiMap;
-
-import java.util.Map;
-import java.util.Set;
-
-/** A {@link BluetoothDetailsController} that manages ambient volume control preferences. */
-public class BluetoothDetailsAmbientVolumePreferenceController extends
- BluetoothDetailsController implements Preference.OnPreferenceChangeListener,
- HearingDeviceLocalDataManager.OnDeviceLocalDataChangeListener, OnStart, OnStop,
- AmbientVolumeController.AmbientVolumeControlCallback, BluetoothCallback {
+/** A {@link BluetoothDetailsController} that manages ambient volume preference. */
+public class BluetoothDetailsAmbientVolumePreferenceController extends BluetoothDetailsController
+ implements OnStart, OnStop {
private static final boolean DEBUG = true;
private static final String TAG = "AmbientPrefController";
static final String KEY_AMBIENT_VOLUME = "ambient_volume";
static final String KEY_AMBIENT_VOLUME_SLIDER = "ambient_volume_slider";
- private static final int ORDER_AMBIENT_VOLUME_CONTROL_UNIFIED = 0;
- private static final int ORDER_AMBIENT_VOLUME_CONTROL_SEPARATED = 1;
private final LocalBluetoothManager mBluetoothManager;
- private final Set<CachedBluetoothDevice> mCachedDevices = new ArraySet<>();
- private final BiMap<Integer, BluetoothDevice> mSideToDeviceMap = HashBiMap.create();
- private final BiMap<Integer, SeekBarPreference> mSideToSliderMap = HashBiMap.create();
- private final HearingDeviceLocalDataManager mLocalDataManager;
- private final AmbientVolumeController mVolumeController;
-
- @Nullable
- private PreferenceCategory mDeviceControls;
@Nullable
private AmbientVolumePreference mPreference;
@Nullable
- private Toast mToast;
+ private AmbientVolumeUiController mAmbientUiController;
public BluetoothDetailsAmbientVolumePreferenceController(@NonNull Context context,
@NonNull LocalBluetoothManager manager,
@@ -99,45 +60,42 @@
@NonNull Lifecycle lifecycle) {
super(context, fragment, device, lifecycle);
mBluetoothManager = manager;
- mLocalDataManager = new HearingDeviceLocalDataManager(context);
- mLocalDataManager.setOnDeviceLocalDataChangeListener(this,
- ThreadUtils.getBackgroundExecutor());
- mVolumeController = new AmbientVolumeController(manager.getProfileManager(), this);
}
@VisibleForTesting
- BluetoothDetailsAmbientVolumePreferenceController(@NonNull Context context,
+ public BluetoothDetailsAmbientVolumePreferenceController(@NonNull Context context,
@NonNull LocalBluetoothManager manager,
@NonNull PreferenceFragmentCompat fragment,
@NonNull CachedBluetoothDevice device,
@NonNull Lifecycle lifecycle,
- @NonNull HearingDeviceLocalDataManager localSettings,
- @NonNull AmbientVolumeController volumeController) {
+ @NonNull AmbientVolumeUiController uiController) {
super(context, fragment, device, lifecycle);
mBluetoothManager = manager;
- mLocalDataManager = localSettings;
- mVolumeController = volumeController;
+ mAmbientUiController = uiController;
}
@Override
protected void init(PreferenceScreen screen) {
- mDeviceControls = screen.findPreference(KEY_HEARING_DEVICE_GROUP);
- if (mDeviceControls == null) {
+ PreferenceCategory deviceControls = screen.findPreference(KEY_HEARING_DEVICE_GROUP);
+ if (deviceControls == null) {
return;
}
- loadDevices();
+ mPreference = new AmbientVolumePreference(deviceControls.getContext());
+ mPreference.setKey(KEY_AMBIENT_VOLUME);
+ mPreference.setOrder(ORDER_AMBIENT_VOLUME);
+ deviceControls.addPreference(mPreference);
+
+ mAmbientUiController = new AmbientVolumeUiController(mContext, mBluetoothManager,
+ mPreference);
+ mAmbientUiController.loadDevice(mCachedDevice);
}
@Override
public void onStart() {
ThreadUtils.postOnBackgroundThread(() -> {
- mBluetoothManager.getEventManager().registerCallback(this);
- mLocalDataManager.start();
- mCachedDevices.forEach(device -> {
- device.registerCallback(ThreadUtils.getBackgroundExecutor(), this);
- mVolumeController.registerCallback(ThreadUtils.getBackgroundExecutor(),
- device.getDevice());
- });
+ if (mAmbientUiController != null) {
+ mAmbientUiController.start();
+ }
});
}
@@ -153,12 +111,9 @@
@Override
public void onStop() {
ThreadUtils.postOnBackgroundThread(() -> {
- mBluetoothManager.getEventManager().unregisterCallback(this);
- mLocalDataManager.stop();
- mCachedDevices.forEach(device -> {
- device.unregisterCallback(this);
- mVolumeController.unregisterCallback(device.getDevice());
- });
+ if (mAmbientUiController != null) {
+ mAmbientUiController.stop();
+ }
});
}
@@ -167,16 +122,8 @@
if (!isAvailable()) {
return;
}
- boolean shouldShowAmbientControl = isAmbientControlAvailable();
- if (shouldShowAmbientControl) {
- if (mPreference != null) {
- mPreference.setVisible(true);
- }
- loadRemoteDataToUi();
- } else {
- if (mPreference != null) {
- mPreference.setVisible(false);
- }
+ if (mAmbientUiController != null) {
+ mAmbientUiController.refresh();
}
}
@@ -191,424 +138,4 @@
public String getPreferenceKey() {
return KEY_AMBIENT_VOLUME;
}
-
- @Override
- public boolean onPreferenceChange(@NonNull Preference preference, @Nullable Object newValue) {
- if (preference instanceof SeekBarPreference && newValue instanceof final Integer value) {
- final int side = mSideToSliderMap.inverse().getOrDefault(preference, SIDE_INVALID);
- if (DEBUG) {
- Log.d(TAG, "onPreferenceChange: side=" + side + ", value=" + value);
- }
- setVolumeIfValid(side, 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 {
- setAmbientRunnable.run();
- }
- return true;
- }
- return false;
- }
-
- @Override
- public void onProfileConnectionStateChanged(@NonNull CachedBluetoothDevice cachedDevice,
- int state, int bluetoothProfile) {
- if (bluetoothProfile == BluetoothProfile.VOLUME_CONTROL
- && state == BluetoothProfile.STATE_CONNECTED
- && mCachedDevices.contains(cachedDevice)) {
- // After VCP connected, AICS may not ready yet and still return invalid value, delay
- // a while to wait AICS ready as a workaround
- mContext.getMainThreadHandler().postDelayed(this::refresh, 1000L);
- }
- }
-
- @Override
- public void onDeviceAttributesChanged() {
- mCachedDevices.forEach(device -> {
- device.unregisterCallback(this);
- mVolumeController.unregisterCallback(device.getDevice());
- });
- mContext.getMainExecutor().execute(() -> {
- loadDevices();
- if (!mCachedDevices.isEmpty()) {
- refresh();
- }
- ThreadUtils.postOnBackgroundThread(() ->
- mCachedDevices.forEach(device -> {
- device.registerCallback(ThreadUtils.getBackgroundExecutor(), this);
- mVolumeController.registerCallback(ThreadUtils.getBackgroundExecutor(),
- device.getDevice());
- })
- );
- });
- }
-
- @Override
- public void onDeviceLocalDataChange(@NonNull String address, @Nullable Data data) {
- if (data == null) {
- // The local data is removed because the device is unpaired, do nothing
- return;
- }
- for (BluetoothDevice device : mSideToDeviceMap.values()) {
- if (device.getAnonymizedAddress().equals(address)) {
- mContext.getMainExecutor().execute(() -> loadLocalDataToUi(device));
- return;
- }
- }
- }
-
- @Override
- public void onVolumeControlServiceConnected() {
- mCachedDevices.forEach(
- device -> mVolumeController.registerCallback(ThreadUtils.getBackgroundExecutor(),
- device.getDevice()));
- }
-
- @Override
- public void onAmbientChanged(@NonNull BluetoothDevice device, int gainSettings) {
- if (DEBUG) {
- Log.d(TAG, "onAmbientChanged, value:" + gainSettings + ", device:" + device);
- }
- Data data = mLocalDataManager.get(device);
- boolean isInitiatedFromUi = (isControlExpanded() && data.ambient() == gainSettings)
- || (!isControlExpanded() && data.groupAmbient() == gainSettings);
- if (isInitiatedFromUi) {
- // The change is initiated from UI, no need to update UI
- return;
- }
-
- // We have to check if we need to expand the controls by getting all remote
- // device's ambient value, delay for a while to wait all remote devices update
- // to the latest value to avoid unnecessary expand action.
- 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);
- mContext.getMainExecutor().execute(() -> {
- showErrorToast();
- refresh();
- });
- }
-
- private void loadDevices() {
- mSideToDeviceMap.clear();
- mCachedDevices.clear();
- if (VALID_SIDES.contains(mCachedDevice.getDeviceSide())
- && mCachedDevice.getBondState() == BOND_BONDED) {
- mSideToDeviceMap.put(mCachedDevice.getDeviceSide(), mCachedDevice.getDevice());
- mCachedDevices.add(mCachedDevice);
- }
- for (CachedBluetoothDevice memberDevice : mCachedDevice.getMemberDevice()) {
- if (VALID_SIDES.contains(memberDevice.getDeviceSide())
- && memberDevice.getBondState() == BOND_BONDED) {
- mSideToDeviceMap.put(memberDevice.getDeviceSide(), memberDevice.getDevice());
- mCachedDevices.add(memberDevice);
- }
- }
- createAmbientVolumePreference();
- createSliderPreferences();
- if (mPreference != null) {
- mPreference.setExpandable(mSideToDeviceMap.size() > 1);
- mPreference.setSliders((mSideToSliderMap));
- }
- }
-
- private void createAmbientVolumePreference() {
- if (mPreference != null || mDeviceControls == null) {
- return;
- }
-
- mPreference = new AmbientVolumePreference(mDeviceControls.getContext());
- mPreference.setKey(KEY_AMBIENT_VOLUME);
- mPreference.setOrder(ORDER_AMBIENT_VOLUME);
- 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);
- }
- }
-
- private void createSliderPreferences() {
- mSideToDeviceMap.forEach((s, d) ->
- createSliderPreference(s, ORDER_AMBIENT_VOLUME_CONTROL_SEPARATED + s));
- createSliderPreference(SIDE_UNIFIED, ORDER_AMBIENT_VOLUME_CONTROL_UNIFIED);
- }
-
- private void createSliderPreference(int side, int order) {
- if (mSideToSliderMap.containsKey(side) || mDeviceControls == null) {
- return;
- }
- SeekBarPreference preference = new SeekBarPreference(mDeviceControls.getContext());
- preference.setKey(KEY_AMBIENT_VOLUME_SLIDER + "_" + side);
- preference.setOrder(order);
- preference.setOnPreferenceChangeListener(this);
- if (side == SIDE_LEFT) {
- preference.setTitle(mContext.getString(R.string.bluetooth_ambient_volume_control_left));
- } else if (side == SIDE_RIGHT) {
- preference.setTitle(
- mContext.getString(R.string.bluetooth_ambient_volume_control_right));
- }
- mSideToSliderMap.put(side, preference);
- }
-
- /** Refreshes the control UI visibility and enabled state. */
- private void refreshControlUi() {
- if (mPreference != null) {
- boolean isAnySliderEnabled = false;
- for (Map.Entry<Integer, BluetoothDevice> entry : mSideToDeviceMap.entrySet()) {
- final int side = entry.getKey();
- final BluetoothDevice device = entry.getValue();
- final boolean enabled = isDeviceConnectedToVcp(device)
- && mVolumeController.isAmbientControlAvailable(device);
- isAnySliderEnabled |= enabled;
- mPreference.setSliderEnabled(side, enabled);
- }
- mPreference.setSliderEnabled(SIDE_UNIFIED, isAnySliderEnabled);
- mPreference.updateLayout();
- }
- }
-
- /** Sets the volume to the corresponding control slider. */
- private void setVolumeIfValid(int side, int volume) {
- if (volume == INVALID_VOLUME) {
- return;
- }
- if (mPreference != null) {
- mPreference.setSliderValue(side, volume);
- }
- // Update new value to local data
- if (side == SIDE_UNIFIED) {
- mSideToDeviceMap.forEach((s, d) -> mLocalDataManager.updateGroupAmbient(d, volume));
- } else {
- mLocalDataManager.updateAmbient(mSideToDeviceMap.get(side), volume);
- }
- }
-
- private void loadLocalDataToUi() {
- mSideToDeviceMap.forEach((s, d) -> loadLocalDataToUi(d));
- }
-
- private void loadLocalDataToUi(BluetoothDevice device) {
- final Data data = mLocalDataManager.get(device);
- if (DEBUG) {
- Log.d(TAG, "loadLocalDataToUi, data=" + data + ", device=" + device);
- }
- final int side = mSideToDeviceMap.inverse().getOrDefault(device, SIDE_INVALID);
- if (isDeviceConnectedToVcp(device) && !isControlMuted()) {
- setVolumeIfValid(side, data.ambient());
- setVolumeIfValid(SIDE_UNIFIED, data.groupAmbient());
- }
- setControlExpanded(data.ambientControlExpanded());
- refreshControlUi();
- }
-
- private void loadRemoteDataToUi() {
- BluetoothDevice leftDevice = mSideToDeviceMap.get(SIDE_LEFT);
- AmbientVolumeController.RemoteAmbientState leftState =
- mVolumeController.refreshAmbientState(leftDevice);
- BluetoothDevice rightDevice = mSideToDeviceMap.get(SIDE_RIGHT);
- AmbientVolumeController.RemoteAmbientState rightState =
- mVolumeController.refreshAmbientState(rightDevice);
- if (DEBUG) {
- Log.d(TAG, "loadRemoteDataToUi, left=" + leftState + ", right=" + rightState);
- }
-
- if (mPreference != null) {
- mSideToDeviceMap.forEach((side, device) -> {
- int ambientMax = mVolumeController.getAmbientMax(device);
- int ambientMin = mVolumeController.getAmbientMin(device);
- if (ambientMin != ambientMax) {
- mPreference.setSliderRange(side, ambientMin, ambientMax);
- mPreference.setSliderRange(SIDE_UNIFIED, ambientMin, ambientMax);
- }
- });
- }
-
- // Update ambient volume
- final int leftAmbient = leftState != null ? leftState.gainSetting() : INVALID_VOLUME;
- final int rightAmbient = rightState != null ? rightState.gainSetting() : INVALID_VOLUME;
- if (isControlExpanded()) {
- setVolumeIfValid(SIDE_LEFT, leftAmbient);
- setVolumeIfValid(SIDE_RIGHT, rightAmbient);
- } else {
- if (leftAmbient != rightAmbient && leftAmbient != INVALID_VOLUME
- && rightAmbient != INVALID_VOLUME) {
- setVolumeIfValid(SIDE_LEFT, leftAmbient);
- setVolumeIfValid(SIDE_RIGHT, rightAmbient);
- setControlExpanded(true);
- } else {
- int unifiedAmbient = leftAmbient != INVALID_VOLUME ? leftAmbient : rightAmbient;
- setVolumeIfValid(SIDE_UNIFIED, unifiedAmbient);
- }
- }
- // 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();
- }
-
- /** Check if any device in the group has valid ambient control points */
- private boolean isAmbientControlAvailable() {
- for (BluetoothDevice device : mSideToDeviceMap.values()) {
- // Found ambient local data for this device, show the ambient control
- if (mLocalDataManager.get(device).hasAmbientData()) {
- return true;
- }
- // Found remote ambient control points on this device, show the ambient control
- if (mVolumeController.isAmbientControlAvailable(device)) {
- return true;
- }
- }
- return false;
- }
-
- private boolean isControlExpanded() {
- return mPreference != null && mPreference.isExpanded();
- }
-
- private void setControlExpanded(boolean expanded) {
- if (mPreference != null && mPreference.isExpanded() != expanded) {
- mPreference.setExpanded(expanded);
- }
- mSideToDeviceMap.forEach((s, d) -> {
- // Update new value to local data
- mLocalDataManager.updateAmbientControlExpanded(d, expanded);
- });
- }
-
- private boolean isControlMuted() {
- return mPreference != null && mPreference.isMuted();
- }
-
- private void initLocalDataIfNeeded() {
- int smallerVolumeAmongGroup = Integer.MAX_VALUE;
- for (BluetoothDevice device : mSideToDeviceMap.values()) {
- Data data = mLocalDataManager.get(device);
- if (data.ambient() != INVALID_VOLUME) {
- smallerVolumeAmongGroup = Math.min(data.ambient(), smallerVolumeAmongGroup);
- } else if (data.groupAmbient() != INVALID_VOLUME) {
- // Initialize side ambient from group ambient value
- mLocalDataManager.updateAmbient(device, data.groupAmbient());
- }
- }
- if (smallerVolumeAmongGroup != Integer.MAX_VALUE) {
- for (BluetoothDevice device : mSideToDeviceMap.values()) {
- Data data = mLocalDataManager.get(device);
- if (data.groupAmbient() == INVALID_VOLUME) {
- // Initialize group ambient from smaller side ambient value
- mLocalDataManager.updateGroupAmbient(device, smallerVolumeAmongGroup);
- }
- }
- }
- }
-
- 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()
- .getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED;
- }
-
- private void showErrorToast() {
- if (mToast != null) {
- mToast.cancel();
- }
- mToast = Toast.makeText(mContext, R.string.bluetooth_ambient_volume_error,
- Toast.LENGTH_SHORT);
- mToast.show();
- }
}
diff --git a/tests/robotests/src/com/android/settings/bluetooth/AmbientVolumePreferenceTest.java b/tests/robotests/src/com/android/settings/bluetooth/AmbientVolumePreferenceTest.java
index ec406c4..115f642 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/AmbientVolumePreferenceTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/AmbientVolumePreferenceTest.java
@@ -26,8 +26,10 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.util.ArrayMap;
import android.view.View;
@@ -40,6 +42,7 @@
import com.android.settings.R;
import com.android.settings.widget.SeekBarPreference;
+import com.android.settingslib.bluetooth.AmbientVolumeUi;
import org.junit.Before;
import org.junit.Rule;
@@ -69,14 +72,14 @@
@Spy
private Context mContext = ApplicationProvider.getApplicationContext();
@Mock
- private AmbientVolumePreference.OnIconClickListener mListener;
+ private AmbientVolumeUi.AmbientVolumeUiListener mListener;
@Mock
private View mItemView;
private AmbientVolumePreference mPreference;
private ImageView mExpandIcon;
private ImageView mVolumeIcon;
- private final Map<Integer, SeekBarPreference> mSideToSlidersMap = new ArrayMap<>();
+ private final Map<Integer, BluetoothDevice> mSideToDeviceMap = new ArrayMap<>();
@Before
public void setUp() {
@@ -84,13 +87,27 @@
PreferenceScreen preferenceScreen = preferenceManager.createPreferenceScreen(mContext);
mPreference = new AmbientVolumePreference(mContext);
mPreference.setKey(KEY_AMBIENT_VOLUME);
- mPreference.setOnIconClickListener(mListener);
+ mPreference.setListener(mListener);
mPreference.setExpandable(true);
mPreference.setMutable(true);
preferenceScreen.addPreference(mPreference);
- prepareSliders();
- mPreference.setSliders(mSideToSlidersMap);
+ prepareDevices();
+ mPreference.setupSliders(mSideToDeviceMap);
+ mPreference.getSliders().forEach((side, slider) -> {
+ 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);
+ }
+ });
mExpandIcon = new ImageView(mContext);
mVolumeIcon = new ImageView(mContext);
@@ -206,33 +223,16 @@
private void assertControlUiCorrect() {
final boolean expanded = mPreference.isExpanded();
- assertThat(mSideToSlidersMap.get(SIDE_UNIFIED).isVisible()).isEqualTo(!expanded);
- assertThat(mSideToSlidersMap.get(SIDE_LEFT).isVisible()).isEqualTo(expanded);
- assertThat(mSideToSlidersMap.get(SIDE_RIGHT).isVisible()).isEqualTo(expanded);
+ Map<Integer, SeekBarPreference> sliders = mPreference.getSliders();
+ assertThat(sliders.get(SIDE_UNIFIED).isVisible()).isEqualTo(!expanded);
+ assertThat(sliders.get(SIDE_LEFT).isVisible()).isEqualTo(expanded);
+ assertThat(sliders.get(SIDE_RIGHT).isVisible()).isEqualTo(expanded);
final float rotation = expanded ? ROTATION_EXPANDED : ROTATION_COLLAPSED;
assertThat(mExpandIcon.getRotation()).isEqualTo(rotation);
}
- private void prepareSliders() {
- prepareSlider(SIDE_UNIFIED);
- prepareSlider(SIDE_LEFT);
- prepareSlider(SIDE_RIGHT);
- }
-
- 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);
+ private void prepareDevices() {
+ mSideToDeviceMap.put(SIDE_LEFT, mock(BluetoothDevice.class));
+ mSideToDeviceMap.put(SIDE_RIGHT, mock(BluetoothDevice.class));
}
}
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceControllerTest.java
index 975d3b4..fb10d09 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceControllerTest.java
@@ -16,46 +16,25 @@
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;
-import static com.android.settings.bluetooth.BluetoothDetailsAmbientVolumePreferenceController.KEY_AMBIENT_VOLUME_SLIDER;
import static com.android.settings.bluetooth.BluetoothDetailsHearingDeviceController.KEY_HEARING_DEVICE_GROUP;
-import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_LEFT;
-import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_RIGHT;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import static org.robolectric.Shadows.shadowOf;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothProfile;
-import android.content.ContentResolver;
import android.os.Handler;
-import android.os.Looper;
-import android.provider.Settings;
import androidx.preference.PreferenceCategory;
import com.android.settings.testutils.shadow.ShadowThreadUtils;
-import com.android.settings.widget.SeekBarPreference;
-import com.android.settingslib.bluetooth.AmbientVolumeController;
+import com.android.settingslib.bluetooth.AmbientVolumeUiController;
import com.android.settingslib.bluetooth.BluetoothEventManager;
-import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.bluetooth.HearingDeviceLocalDataManager;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.settingslib.bluetooth.VolumeControlProfile;
@@ -69,41 +48,19 @@
import org.mockito.junit.MockitoRule;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
-import org.robolectric.annotation.Implementation;
-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;
/** Tests for {@link BluetoothDetailsAmbientVolumePreferenceController}. */
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {
- BluetoothDetailsAmbientVolumePreferenceControllerTest.ShadowGlobal.class,
ShadowThreadUtils.class
})
public class BluetoothDetailsAmbientVolumePreferenceControllerTest extends
BluetoothDetailsControllerTestBase {
-
@Rule
public final MockitoRule mMockitoRule = MockitoJUnit.rule();
- private static final String LEFT_CONTROL_KEY = KEY_AMBIENT_VOLUME_SLIDER + "_" + SIDE_LEFT;
- private static final String RIGHT_CONTROL_KEY = KEY_AMBIENT_VOLUME_SLIDER + "_" + SIDE_RIGHT;
- private static final String TEST_ADDRESS = "00:00:00:00:11";
- private static final String TEST_MEMBER_ADDRESS = "00:00:00:00:22";
-
- @Mock
- private CachedBluetoothDevice mCachedMemberDevice;
- @Mock
- private BluetoothDevice mDevice;
- @Mock
- private BluetoothDevice mMemberDevice;
- @Mock
- private HearingDeviceLocalDataManager mLocalDataManager;
@Mock
private LocalBluetoothManager mBluetoothManager;
@Mock
@@ -113,9 +70,9 @@
@Mock
private VolumeControlProfile mVolumeControlProfile;
@Mock
- private AmbientVolumeController mVolumeController;
- @Mock
private Handler mTestHandler;
+ @Mock
+ private AmbientVolumeUiController mUiController;
private BluetoothDetailsAmbientVolumePreferenceController mController;
@@ -124,24 +81,16 @@
super.setUp();
mContext = spy(mContext);
+
+ when(mBluetoothManager.getProfileManager()).thenReturn(mProfileManager);
+ when(mBluetoothManager.getEventManager()).thenReturn(mEventManager);
+ mController = spy(
+ new BluetoothDetailsAmbientVolumePreferenceController(mContext, mBluetoothManager,
+ mFragment, mCachedDevice, mLifecycle, mUiController));
+
PreferenceCategory deviceControls = new PreferenceCategory(mContext);
deviceControls.setKey(KEY_HEARING_DEVICE_GROUP);
mScreen.addPreference(deviceControls);
- mController = spy(
- new BluetoothDetailsAmbientVolumePreferenceController(mContext, mBluetoothManager,
- mFragment, mCachedDevice, mLifecycle, mLocalDataManager,
- mVolumeController));
-
- when(mBluetoothManager.getEventManager()).thenReturn(mEventManager);
- when(mBluetoothManager.getProfileManager()).thenReturn(mProfileManager);
- when(mProfileManager.getVolumeControlProfile()).thenReturn(mVolumeControlProfile);
- when(mVolumeControlProfile.getConnectionStatus(mDevice)).thenReturn(
- 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(
@@ -152,283 +101,42 @@
}
@Test
- public void init_deviceWithoutMember_controlNotExpandable() {
- prepareDevice(/* hasMember= */ false);
-
+ public void init_preferenceAdded() {
mController.init(mScreen);
AmbientVolumePreference preference = mScreen.findPreference(KEY_AMBIENT_VOLUME);
assertThat(preference).isNotNull();
- assertThat(preference.isExpandable()).isFalse();
}
@Test
- public void init_deviceWithMember_controlExpandable() {
- prepareDevice(/* hasMember= */ true);
+ public void refresh_deviceNotSupportVcp_verifyUiControllerNoRefresh() {
+ when(mCachedDevice.getProfiles()).thenReturn(List.of());
- mController.init(mScreen);
+ mController.refresh();
- AmbientVolumePreference preference = mScreen.findPreference(KEY_AMBIENT_VOLUME);
- assertThat(preference).isNotNull();
- assertThat(preference.isExpandable()).isTrue();
+ verify(mUiController, never()).refresh();
}
@Test
- public void onDeviceLocalDataChange_noMemberAndExpanded_uiCorrectAndDataUpdated() {
- prepareDevice(/* hasMember= */ false);
- mController.init(mScreen);
- HearingDeviceLocalDataManager.Data data = new HearingDeviceLocalDataManager.Data.Builder()
- .ambient(0).groupAmbient(0).ambientControlExpanded(true).build();
- when(mLocalDataManager.get(mDevice)).thenReturn(data);
+ public void refresh_deviceSupportVcp_verifyUiControllerRefresh() {
+ when(mCachedDevice.getProfiles()).thenReturn(List.of(mVolumeControlProfile));
- mController.onDeviceLocalDataChange(TEST_ADDRESS, data);
- shadowOf(Looper.getMainLooper()).idle();
+ mController.refresh();
- AmbientVolumePreference preference = mScreen.findPreference(KEY_AMBIENT_VOLUME);
- assertThat(preference).isNotNull();
- assertThat(preference.isExpanded()).isFalse();
- verifyDeviceDataUpdated(mDevice);
+ verify(mUiController).refresh();
}
@Test
- public void onDeviceLocalDataChange_noMemberAndCollapsed_uiCorrectAndDataUpdated() {
- prepareDevice(/* hasMember= */ false);
- mController.init(mScreen);
- HearingDeviceLocalDataManager.Data data = new HearingDeviceLocalDataManager.Data.Builder()
- .ambient(0).groupAmbient(0).ambientControlExpanded(false).build();
- when(mLocalDataManager.get(mDevice)).thenReturn(data);
-
- mController.onDeviceLocalDataChange(TEST_ADDRESS, data);
- shadowOf(Looper.getMainLooper()).idle();
-
- AmbientVolumePreference preference = mScreen.findPreference(KEY_AMBIENT_VOLUME);
- assertThat(preference).isNotNull();
- assertThat(preference.isExpanded()).isFalse();
- verifyDeviceDataUpdated(mDevice);
- }
-
- @Test
- public void onDeviceLocalDataChange_hasMemberAndExpanded_uiCorrectAndDataUpdated() {
- prepareDevice(/* hasMember= */ true);
- mController.init(mScreen);
- HearingDeviceLocalDataManager.Data data = new HearingDeviceLocalDataManager.Data.Builder()
- .ambient(0).groupAmbient(0).ambientControlExpanded(true).build();
- when(mLocalDataManager.get(mDevice)).thenReturn(data);
-
- mController.onDeviceLocalDataChange(TEST_ADDRESS, data);
- shadowOf(Looper.getMainLooper()).idle();
-
- AmbientVolumePreference preference = mScreen.findPreference(KEY_AMBIENT_VOLUME);
- assertThat(preference).isNotNull();
- assertThat(preference.isExpanded()).isTrue();
- verifyDeviceDataUpdated(mDevice);
- }
-
- @Test
- public void onDeviceLocalDataChange_hasMemberAndCollapsed_uiCorrectAndDataUpdated() {
- prepareDevice(/* hasMember= */ true);
- mController.init(mScreen);
- HearingDeviceLocalDataManager.Data data = new HearingDeviceLocalDataManager.Data.Builder()
- .ambient(0).groupAmbient(0).ambientControlExpanded(false).build();
- when(mLocalDataManager.get(mDevice)).thenReturn(data);
-
- mController.onDeviceLocalDataChange(TEST_ADDRESS, data);
- shadowOf(Looper.getMainLooper()).idle();
-
- AmbientVolumePreference preference = mScreen.findPreference(KEY_AMBIENT_VOLUME);
- assertThat(preference).isNotNull();
- assertThat(preference.isExpanded()).isFalse();
- verifyDeviceDataUpdated(mDevice);
- }
-
- @Test
- public void onStart_localDataManagerStartAndCallbackRegistered() {
- prepareDevice(/* hasMember= */ true);
- mController.init(mScreen);
-
+ public void onStart_verifyUiControllerStart() {
mController.onStart();
- verify(mLocalDataManager, atLeastOnce()).start();
- verify(mVolumeController).registerCallback(any(Executor.class), eq(mDevice));
- verify(mVolumeController).registerCallback(any(Executor.class), eq(mMemberDevice));
- verify(mCachedDevice).registerCallback(any(Executor.class),
- any(CachedBluetoothDevice.Callback.class));
- verify(mCachedMemberDevice).registerCallback(any(Executor.class),
- any(CachedBluetoothDevice.Callback.class));
+ verify(mUiController).start();
}
@Test
- public void onStop_localDataManagerStopAndCallbackUnregistered() {
- prepareDevice(/* hasMember= */ true);
- mController.init(mScreen);
-
+ public void onStop_verifyUiControllerStop() {
mController.onStop();
- verify(mLocalDataManager).stop();
- verify(mVolumeController).unregisterCallback(mDevice);
- verify(mVolumeController).unregisterCallback(mMemberDevice);
- verify(mCachedDevice).unregisterCallback(any(CachedBluetoothDevice.Callback.class));
- verify(mCachedMemberDevice).unregisterCallback(any(CachedBluetoothDevice.Callback.class));
- }
-
- @Test
- public void onDeviceAttributesChanged_newDevice_newPreference() {
- prepareDevice(/* hasMember= */ false);
- mController.init(mScreen);
-
- // check the right control is null before onDeviceAttributesChanged()
- SeekBarPreference leftControl = mScreen.findPreference(LEFT_CONTROL_KEY);
- SeekBarPreference rightControl = mScreen.findPreference(RIGHT_CONTROL_KEY);
- assertThat(leftControl).isNotNull();
- assertThat(rightControl).isNull();
-
- prepareDevice(/* hasMember= */ true);
- mController.onDeviceAttributesChanged();
- shadowOf(Looper.getMainLooper()).idle();
-
- // check the right control is created after onDeviceAttributesChanged()
- SeekBarPreference updatedLeftControl = mScreen.findPreference(LEFT_CONTROL_KEY);
- SeekBarPreference updatedRightControl = mScreen.findPreference(RIGHT_CONTROL_KEY);
- assertThat(updatedLeftControl).isEqualTo(leftControl);
- assertThat(updatedRightControl).isNotNull();
- }
-
- @Test
- public void onAmbientChanged_refreshWhenNotInitiateFromUi() {
- prepareDevice(/* hasMember= */ false);
- mController.init(mScreen);
- final int testAmbient = 10;
- HearingDeviceLocalDataManager.Data data = new HearingDeviceLocalDataManager.Data.Builder()
- .ambient(testAmbient)
- .groupAmbient(testAmbient)
- .ambientControlExpanded(false)
- .build();
- when(mLocalDataManager.get(mDevice)).thenReturn(data);
- getPreference().setExpanded(true);
-
- mController.onAmbientChanged(mDevice, testAmbient);
- verify(mController, never()).refresh();
-
- final int updatedTestAmbient = 20;
- mController.onAmbientChanged(mDevice, updatedTestAmbient);
- 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);
- when(mCachedDevice.getBondState()).thenReturn(BOND_BONDED);
- when(mDevice.getAddress()).thenReturn(TEST_ADDRESS);
- when(mDevice.getAnonymizedAddress()).thenReturn(TEST_ADDRESS);
- when(mDevice.isConnected()).thenReturn(true);
- if (hasMember) {
- when(mCachedDevice.getMemberDevice()).thenReturn(Set.of(mCachedMemberDevice));
- when(mCachedMemberDevice.getDeviceSide()).thenReturn(SIDE_RIGHT);
- when(mCachedMemberDevice.getDevice()).thenReturn(mMemberDevice);
- when(mCachedMemberDevice.getBondState()).thenReturn(BOND_BONDED);
- when(mMemberDevice.getAddress()).thenReturn(TEST_MEMBER_ADDRESS);
- when(mMemberDevice.getAnonymizedAddress()).thenReturn(TEST_MEMBER_ADDRESS);
- when(mMemberDevice.isConnected()).thenReturn(true);
- }
- }
-
- 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());
- verify(mLocalDataManager, atLeastOnce()).updateAmbientControlExpanded(eq(device),
- anyBoolean());
- }
-
- private AmbientVolumePreference getPreference() {
- return mScreen.findPreference(KEY_AMBIENT_VOLUME);
- }
-
- @Implements(value = Settings.Global.class)
- public static class ShadowGlobal extends ShadowSettings.ShadowGlobal {
- private static final Map<ContentResolver, Map<String, String>> sDataMap = new HashMap<>();
-
- @Implementation
- protected static boolean putStringForUser(
- ContentResolver cr, String name, String value, int userHandle) {
- get(cr).put(name, value);
- return true;
- }
-
- @Implementation
- protected static String getStringForUser(ContentResolver cr, String name, int userHandle) {
- return get(cr).get(name);
- }
-
- private static Map<String, String> get(ContentResolver cr) {
- return sDataMap.computeIfAbsent(cr, k -> new HashMap<>());
- }
+ verify(mUiController).stop();
}
}