diff --git a/res/values/strings.xml b/res/values/strings.xml
index ee80dae7d24..5ab38a48afd 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -174,6 +174,8 @@
Left
Right
+
+ Couldn\u2019t update surroundings
Audio output
diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceController.java b/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceController.java
index 90727035c0b..887c220c99f 100644
--- a/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceController.java
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceController.java
@@ -28,9 +28,11 @@ import static com.android.settingslib.bluetooth.HearingAidInfo.DeviceSide.SIDE_R
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;
@@ -42,9 +44,12 @@ 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.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;
import com.android.settingslib.core.lifecycle.events.OnStart;
@@ -54,12 +59,14 @@ 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 {
+ HearingDeviceLocalDataManager.OnDeviceLocalDataChangeListener, OnStart, OnStop,
+ AmbientVolumeController.AmbientVolumeControlCallback, BluetoothCallback {
private static final boolean DEBUG = true;
private static final String TAG = "AmbientPrefController";
@@ -69,34 +76,45 @@ public class BluetoothDetailsAmbientVolumePreferenceController extends
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 mCachedDevices = new ArraySet<>();
private final BiMap mSideToDeviceMap = HashBiMap.create();
private final BiMap mSideToSliderMap = HashBiMap.create();
private final HearingDeviceLocalDataManager mLocalDataManager;
+ private final AmbientVolumeController mVolumeController;
@Nullable
private PreferenceCategory mDeviceControls;
@Nullable
private AmbientVolumePreference mPreference;
+ @Nullable
+ private Toast mToast;
public BluetoothDetailsAmbientVolumePreferenceController(@NonNull Context context,
+ @NonNull LocalBluetoothManager manager,
@NonNull PreferenceFragmentCompat fragment,
@NonNull CachedBluetoothDevice device,
@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,
+ @NonNull LocalBluetoothManager manager,
@NonNull PreferenceFragmentCompat fragment,
@NonNull CachedBluetoothDevice device,
@NonNull Lifecycle lifecycle,
- @NonNull HearingDeviceLocalDataManager localSettings) {
+ @NonNull HearingDeviceLocalDataManager localSettings,
+ @NonNull AmbientVolumeController volumeController) {
super(context, fragment, device, lifecycle);
+ mBluetoothManager = manager;
mLocalDataManager = localSettings;
+ mVolumeController = volumeController;
}
@Override
@@ -111,19 +129,33 @@ public class BluetoothDetailsAmbientVolumePreferenceController extends
@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());
});
});
}
+ @Override
+ public void onResume() {
+ refresh();
+ }
+
+ @Override
+ public void onPause() {
+ }
+
@Override
public void onStop() {
ThreadUtils.postOnBackgroundThread(() -> {
+ mBluetoothManager.getEventManager().unregisterCallback(this);
mLocalDataManager.stop();
mCachedDevices.forEach(device -> {
device.unregisterCallback(this);
+ mVolumeController.unregisterCallback(device.getDevice());
});
});
}
@@ -133,8 +165,17 @@ public class BluetoothDetailsAmbientVolumePreferenceController extends
if (!isAvailable()) {
return;
}
- // TODO: load data from remote
- loadLocalDataToUi();
+ boolean shouldShowAmbientControl = isAmbientControlAvailable();
+ if (shouldShowAmbientControl) {
+ if (mPreference != null) {
+ mPreference.setVisible(true);
+ }
+ loadRemoteDataToUi();
+ } else {
+ if (mPreference != null) {
+ mPreference.setVisible(false);
+ }
+ }
}
@Override
@@ -160,19 +201,33 @@ public class BluetoothDetailsAmbientVolumePreferenceController extends
setVolumeIfValid(side, value);
if (side == SIDE_UNIFIED) {
- // TODO: set the value on the devices
+ mSideToDeviceMap.forEach((s, d) -> mVolumeController.setAmbient(d, value));
} else {
- // TODO: set the value on the side device
+ final BluetoothDevice device = mSideToDeviceMap.get(side);
+ mVolumeController.setAmbient(device, value);
}
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();
@@ -182,6 +237,8 @@ public class BluetoothDetailsAmbientVolumePreferenceController extends
ThreadUtils.postOnBackgroundThread(() ->
mCachedDevices.forEach(device -> {
device.registerCallback(ThreadUtils.getBackgroundExecutor(), this);
+ mVolumeController.registerCallback(ThreadUtils.getBackgroundExecutor(),
+ device.getDevice());
})
);
});
@@ -201,6 +258,41 @@ public class BluetoothDetailsAmbientVolumePreferenceController extends
}
}
+ @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 onCommandFailed(@NonNull BluetoothDevice device) {
+ Log.w(TAG, "onCommandFailed, device:" + device);
+ mContext.getMainExecutor().execute(() -> {
+ showErrorToast();
+ refresh();
+ });
+ }
+
private void loadDevices() {
mSideToDeviceMap.clear();
mCachedDevices.clear();
@@ -234,6 +326,11 @@ public class BluetoothDetailsAmbientVolumePreferenceController extends
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());
});
@@ -269,6 +366,16 @@ public class BluetoothDetailsAmbientVolumePreferenceController extends
/** Refreshes the control UI visibility and enabled state. */
private void refreshControlUi() {
if (mPreference != null) {
+ boolean isAnySliderEnabled = false;
+ for (Map.Entry 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();
}
}
@@ -299,12 +406,74 @@ public class BluetoothDetailsAmbientVolumePreferenceController extends
Log.d(TAG, "loadLocalDataToUi, data=" + data + ", device=" + device);
}
final int side = mSideToDeviceMap.inverse().getOrDefault(device, SIDE_INVALID);
- setVolumeIfValid(side, data.ambient());
- setVolumeIfValid(SIDE_UNIFIED, data.groupAmbient());
+ if (isDeviceConnectedToVcp(device)) {
+ 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();
+
+ 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();
}
@@ -318,4 +487,41 @@ public class BluetoothDetailsAmbientVolumePreferenceController extends
mLocalDataManager.updateAmbientControlExpanded(d, expanded);
});
}
+
+ 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 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/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceController.java b/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceController.java
index 7236d8cecc8..8af08792180 100644
--- a/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceController.java
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsHearingDeviceController.java
@@ -110,7 +110,7 @@ public class BluetoothDetailsHearingDeviceController extends BluetoothDetailsCon
}
if (com.android.settingslib.flags.Flags.hearingDevicesAmbientVolumeControl()) {
mControllers.add(new BluetoothDetailsAmbientVolumePreferenceController(mContext,
- mFragment, mCachedDevice, mLifecycle));
+ mManager, mFragment, mCachedDevice, mLifecycle));
}
}
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceControllerTest.java
index 71da4b272c6..b7aaab4527a 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceControllerTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsAmbientVolumePreferenceControllerTest.java
@@ -29,14 +29,19 @@ 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;
@@ -44,8 +49,13 @@ 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.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;
import org.junit.Before;
import org.junit.Rule;
@@ -90,6 +100,18 @@ public class BluetoothDetailsAmbientVolumePreferenceControllerTest extends
private BluetoothDevice mMemberDevice;
@Mock
private HearingDeviceLocalDataManager mLocalDataManager;
+ @Mock
+ private LocalBluetoothManager mBluetoothManager;
+ @Mock
+ private BluetoothEventManager mEventManager;
+ @Mock
+ private LocalBluetoothProfileManager mProfileManager;
+ @Mock
+ private VolumeControlProfile mVolumeControlProfile;
+ @Mock
+ private AmbientVolumeController mVolumeController;
+ @Mock
+ private Handler mTestHandler;
private BluetoothDetailsAmbientVolumePreferenceController mController;
@@ -97,11 +119,29 @@ public class BluetoothDetailsAmbientVolumePreferenceControllerTest extends
public void setUp() {
super.setUp();
+ mContext = spy(mContext);
PreferenceCategory deviceControls = new PreferenceCategory(mContext);
deviceControls.setKey(KEY_HEARING_DEVICE_GROUP);
mScreen.addPreference(deviceControls);
- mController = new BluetoothDetailsAmbientVolumePreferenceController(mContext, mFragment,
- mCachedDevice, mLifecycle, mLocalDataManager);
+ 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(mContext.getMainThreadHandler()).thenReturn(mTestHandler);
+ when(mTestHandler.postDelayed(any(Runnable.class), anyLong())).thenAnswer(
+ invocationOnMock -> {
+ invocationOnMock.getArgument(0, Runnable.class).run();
+ return null;
+ });
}
@Test
@@ -128,10 +168,13 @@ public class BluetoothDetailsAmbientVolumePreferenceControllerTest extends
@Test
public void onDeviceLocalDataChange_noMemberAndExpanded_uiCorrectAndDataUpdated() {
- prepareDevice(/* hasMember= */ false, /* controlExpanded= */ true);
-
+ prepareDevice(/* hasMember= */ false);
mController.init(mScreen);
- mController.onDeviceLocalDataChange(TEST_ADDRESS, prepareEmptyData());
+ 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);
@@ -142,10 +185,13 @@ public class BluetoothDetailsAmbientVolumePreferenceControllerTest extends
@Test
public void onDeviceLocalDataChange_noMemberAndCollapsed_uiCorrectAndDataUpdated() {
- prepareDevice(/* hasMember= */ false, /* controlExpanded= */ false);
-
+ prepareDevice(/* hasMember= */ false);
mController.init(mScreen);
- mController.onDeviceLocalDataChange(TEST_ADDRESS, prepareEmptyData());
+ 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);
@@ -156,10 +202,13 @@ public class BluetoothDetailsAmbientVolumePreferenceControllerTest extends
@Test
public void onDeviceLocalDataChange_hasMemberAndExpanded_uiCorrectAndDataUpdated() {
- prepareDevice(/* hasMember= */ true, /* controlExpanded= */ true);
-
+ prepareDevice(/* hasMember= */ true);
mController.init(mScreen);
- mController.onDeviceLocalDataChange(TEST_ADDRESS, prepareEmptyData());
+ 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);
@@ -170,10 +219,13 @@ public class BluetoothDetailsAmbientVolumePreferenceControllerTest extends
@Test
public void onDeviceLocalDataChange_hasMemberAndCollapsed_uiCorrectAndDataUpdated() {
- prepareDevice(/* hasMember= */ true, /* controlExpanded= */ false);
-
+ prepareDevice(/* hasMember= */ true);
mController.init(mScreen);
- mController.onDeviceLocalDataChange(TEST_ADDRESS, prepareEmptyData());
+ 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);
@@ -185,11 +237,13 @@ public class BluetoothDetailsAmbientVolumePreferenceControllerTest extends
@Test
public void onStart_localDataManagerStartAndCallbackRegistered() {
prepareDevice(/* hasMember= */ true);
-
mController.init(mScreen);
+
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),
@@ -199,11 +253,13 @@ public class BluetoothDetailsAmbientVolumePreferenceControllerTest extends
@Test
public void onStop_localDataManagerStopAndCallbackUnregistered() {
prepareDevice(/* hasMember= */ true);
-
mController.init(mScreen);
+
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));
}
@@ -211,7 +267,6 @@ public class BluetoothDetailsAmbientVolumePreferenceControllerTest extends
@Test
public void onDeviceAttributesChanged_newDevice_newPreference() {
prepareDevice(/* hasMember= */ false);
-
mController.init(mScreen);
// check the right control is null before onDeviceAttributesChanged()
@@ -231,16 +286,34 @@ public class BluetoothDetailsAmbientVolumePreferenceControllerTest extends
assertThat(updatedRightControl).isNotNull();
}
- private void prepareDevice(boolean hasMember) {
- prepareDevice(hasMember, false);
+ @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();
}
- private void prepareDevice(boolean hasMember, boolean controlExpanded) {
+ 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);
@@ -248,14 +321,8 @@ public class BluetoothDetailsAmbientVolumePreferenceControllerTest extends
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);
}
- HearingDeviceLocalDataManager.Data data = new HearingDeviceLocalDataManager.Data.Builder()
- .ambient(0).groupAmbient(0).ambientControlExpanded(controlExpanded).build();
- when(mLocalDataManager.get(any(BluetoothDevice.class))).thenReturn(data);
- }
-
- private HearingDeviceLocalDataManager.Data prepareEmptyData() {
- return new HearingDeviceLocalDataManager.Data.Builder().build();
}
private void verifyDeviceDataUpdated(BluetoothDevice device) {
@@ -265,6 +332,10 @@ public class BluetoothDetailsAmbientVolumePreferenceControllerTest extends
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> sDataMap = new HashMap<>();