[Audiosharing] Avoid dup volume control entries

Test: atest
Bug: 402384034
Flag: com.android.settingslib.flags.enable_le_audio_sharing
Change-Id: I1c975a39106d50746c692586bf682301dd299f40
This commit is contained in:
Yiyi Shen
2025-03-17 11:48:36 +08:00
parent b96eb497da
commit 049835b568
9 changed files with 259 additions and 54 deletions

View File

@@ -277,7 +277,10 @@ public abstract class BluetoothDeviceUpdater implements BluetoothCallback,
}
}
private void removePreference(BluetoothDevice device) {
/**
* Remove the {@link Preference} that represents the {@code device}
*/
protected void removePreference(BluetoothDevice device) {
if (mPreferenceMap.containsKey(device)) {
if (mPreferenceMap.get(device) instanceof BluetoothDevicePreference preference) {
BluetoothDevice prefDevice = preference.getBluetoothDevice().getDevice();
@@ -362,7 +365,7 @@ public abstract class BluetoothDeviceUpdater implements BluetoothCallback,
return mLocalManager.getCachedDeviceManager().getCachedDevicesCopy().contains(cachedDevice);
}
private boolean isDeviceOfMapInCachedDevicesList(BluetoothDevice inputBluetoothDevice) {
protected boolean isDeviceOfMapInCachedDevicesList(BluetoothDevice inputBluetoothDevice) {
Collection<CachedBluetoothDevice> cachedDevices =
mLocalManager.getCachedDeviceManager().getCachedDevicesCopy();
if (cachedDevices == null || cachedDevices.isEmpty()) {

View File

@@ -31,7 +31,6 @@ import com.android.settings.connecteddevice.DevicePreferenceCallback;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.VolumeControlProfile;
public class AudioSharingDeviceVolumeControlUpdater extends BluetoothDeviceUpdater
implements Preference.OnPreferenceClickListener {
@@ -42,7 +41,6 @@ public class AudioSharingDeviceVolumeControlUpdater extends BluetoothDeviceUpdat
static final String PREF_KEY_PREFIX = "audio_sharing_volume_control_";
@Nullable private final LocalBluetoothManager mBtManager;
@Nullable private final VolumeControlProfile mVolumeControl;
public AudioSharingDeviceVolumeControlUpdater(
Context context,
@@ -50,10 +48,6 @@ public class AudioSharingDeviceVolumeControlUpdater extends BluetoothDeviceUpdat
int metricsCategory) {
super(context, devicePreferenceCallback, metricsCategory);
mBtManager = Utils.getLocalBluetoothManager(context);
mVolumeControl =
mBtManager == null
? null
: mBtManager.getProfileManager().getVolumeControlProfile();
}
@Override
@@ -99,6 +93,40 @@ public class AudioSharingDeviceVolumeControlUpdater extends BluetoothDeviceUpdat
}
}
@Override
public void refreshPreference() {
mPreferenceMap.forEach((key, preference) -> {
if (isDeviceOfMapInCachedDevicesList(key)) {
((AudioSharingDeviceVolumePreference) preference).onPreferenceAttributesChanged();
} else {
// Remove staled preference.
Log.d(TAG, "removePreference key: " + key.getAnonymizedAddress());
removePreference(key);
}
});
}
@Override
protected void removePreference(BluetoothDevice device) {
if (mPreferenceMap.containsKey(device)) {
if (mPreferenceMap.get(device) instanceof AudioSharingDeviceVolumePreference pref) {
BluetoothDevice prefDevice = pref.getCachedDevice().getDevice();
// For CSIP device, when it {@link CachedBluetoothDevice}#switchMemberDeviceContent,
// it will change its mDevice and lead to the hashcode change for this preference.
// This will cause unintended remove preference, see b/394765052
if (device.equals(prefDevice) || !mPreferenceMap.containsKey(prefDevice)) {
mDevicePreferenceCallback.onDeviceRemoved(pref);
} else {
Log.w(TAG, "Inconsistent key and preference when removePreference");
}
mPreferenceMap.remove(device);
} else {
mDevicePreferenceCallback.onDeviceRemoved(mPreferenceMap.get(device));
mPreferenceMap.remove(device);
}
}
}
@Override
protected String getPreferenceKeyPrefix() {
return PREF_KEY_PREFIX;
@@ -121,7 +149,4 @@ public class AudioSharingDeviceVolumeControlUpdater extends BluetoothDeviceUpdat
@Override
protected void launchDeviceDetails(Preference preference) {}
@Override
public void refreshPreference() {}
}

View File

@@ -368,6 +368,7 @@ public class AudioSharingDeviceVolumeGroupController extends AudioSharingBasePre
mAssistant.registerServiceCallBack(mExecutor, mBroadcastAssistantCallback);
mVolumeControl.registerCallback(mExecutor, mVolumeControlCallback);
mBluetoothDeviceUpdater.registerCallback();
mBluetoothDeviceUpdater.refreshPreference();
mContentResolver.registerContentObserver(
Settings.Secure.getUriFor(BluetoothUtils.getPrimaryGroupIdUriForBroadcast()),
false,

View File

@@ -104,6 +104,39 @@ public class AudioSharingDeviceVolumePreference extends SeekBarPreference {
handleProgressChange(seekBar.getProgress());
}
@Override
public boolean equals(@Nullable Object o) {
if ((o == null) || !(o instanceof AudioSharingDeviceVolumePreference)) {
return false;
}
return mCachedDevice.equals(
((AudioSharingDeviceVolumePreference) o).mCachedDevice);
}
@Override
public int hashCode() {
return mCachedDevice.hashCode();
}
@Override
@NonNull
public String toString() {
StringBuilder builder = new StringBuilder("Preference{");
builder.append("preference=").append(super.toString());
if (mCachedDevice.getDevice() != null) {
builder.append(", device=").append(mCachedDevice.getDevice().getAnonymizedAddress());
}
builder.append("}");
return builder.toString();
}
void onPreferenceAttributesChanged() {
var unused = ThreadUtils.postOnBackgroundThread(() -> {
String name = mCachedDevice.getName();
AudioSharingUtils.postOnMainThread(mContext, () -> setTitle(name));
});
}
private void handleProgressChange(int progress) {
var unused =
ThreadUtils.postOnBackgroundThread(

View File

@@ -515,9 +515,10 @@ public class AvailableMediaBluetoothDeviceUpdaterTest {
mAudioManager.setMode(audioMode);
mBluetoothDeviceUpdater =
spy(new AvailableMediaBluetoothDeviceUpdater(
mContext, mDevicePreferenceCallback, /* metricsCategory= */ 0));
mContext, mDevicePreferenceCallback, /* metricsCategory= */ 0));
mBluetoothDeviceUpdater.setPrefContext(mContext);
doNothing().when(mBluetoothDeviceUpdater).addPreference(any());
doNothing().when(mBluetoothDeviceUpdater).removePreference(any());
doNothing().when(mBluetoothDeviceUpdater).removePreference(
any(CachedBluetoothDevice.class));
}
}

View File

@@ -429,6 +429,7 @@ public class ConnectedBluetoothDeviceUpdaterTest {
mDevicePreferenceCallback, /* metricsCategory= */ 0));
mBluetoothDeviceUpdater.setPrefContext(mContext);
doNothing().when(mBluetoothDeviceUpdater).addPreference(any());
doNothing().when(mBluetoothDeviceUpdater).removePreference(any());
doNothing().when(mBluetoothDeviceUpdater).removePreference(
any(CachedBluetoothDevice.class));
}
}

View File

@@ -106,7 +106,8 @@ public class SavedBluetoothDeviceUpdaterTest {
mPreference = new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
false, BluetoothDevicePreference.SortType.TYPE_DEFAULT);
doNothing().when(mBluetoothDeviceUpdater).addPreference(any());
doNothing().when(mBluetoothDeviceUpdater).removePreference(any());
doNothing().when(mBluetoothDeviceUpdater).removePreference(
any(CachedBluetoothDevice.class));
mCachedDevices.add(mCachedBluetoothDevice);
when(mBluetoothManager.getCachedDeviceManager()).thenReturn(mDeviceManager);
when(mDeviceManager.getCachedDevicesCopy()).thenReturn(mCachedDevices);

View File

@@ -22,7 +22,6 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -74,13 +73,14 @@ import java.util.List;
@Config(shadows = {ShadowBluetoothUtils.class})
public class AudioSharingDeviceVolumeControlUpdaterTest {
private static final String TEST_DEVICE_NAME = "test";
private static final String TEST_DEVICE_ADDRESS = "XX:XX:XX:XX:XX:11";
private static final String TAG = "AudioSharingVolUpdater";
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@Mock private DevicePreferenceCallback mDevicePreferenceCallback;
@Mock private CachedBluetoothDevice mCachedBluetoothDevice;
@Mock private BluetoothDevice mBluetoothDevice;
@Mock private CachedBluetoothDevice mCachedDevice;
@Mock private BluetoothDevice mDevice;
@Mock private LocalBluetoothManager mLocalBtManager;
@Mock private CachedBluetoothDeviceManager mCachedDeviceManager;
@Mock private LocalBluetoothProfileManager mLocalBtProfileManager;
@@ -104,11 +104,12 @@ public class AudioSharingDeviceVolumeControlUpdaterTest {
List<Long> bisSyncState = new ArrayList<>();
bisSyncState.add(1L);
when(mState.getBisSyncState()).thenReturn(bisSyncState);
doReturn(TEST_DEVICE_NAME).when(mCachedBluetoothDevice).getName();
doReturn(mBluetoothDevice).when(mCachedBluetoothDevice).getDevice();
doReturn(ImmutableSet.of()).when(mCachedBluetoothDevice).getMemberDevice();
when(mDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
when(mCachedDevice.getName()).thenReturn(TEST_DEVICE_NAME);
when(mCachedDevice.getDevice()).thenReturn(mDevice);
when(mCachedDevice.getMemberDevice()).thenReturn(ImmutableSet.of());
mCachedDevices = new ArrayList<>();
mCachedDevices.add(mCachedBluetoothDevice);
mCachedDevices.add(mCachedDevice);
when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(mCachedDevices);
doNothing().when(mDevicePreferenceCallback).onDeviceAdded(any(Preference.class));
doNothing().when(mDevicePreferenceCallback).onDeviceRemoved(any(Preference.class));
@@ -128,7 +129,7 @@ public class AudioSharingDeviceVolumeControlUpdaterTest {
public void onProfileConnectionStateChanged_leaDeviceConnected_noSharing_removesPref() {
setupPreferenceMapWithDevice();
mDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice,
mCachedDevice,
BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.LE_AUDIO);
shadowOf(Looper.getMainLooper()).idle();
@@ -137,7 +138,7 @@ public class AudioSharingDeviceVolumeControlUpdaterTest {
ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class);
mDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice,
mCachedDevice,
BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.LE_AUDIO);
shadowOf(Looper.getMainLooper()).idle();
@@ -145,23 +146,23 @@ public class AudioSharingDeviceVolumeControlUpdaterTest {
verify(mDevicePreferenceCallback).onDeviceRemoved(captor.capture());
assertThat(captor.getValue() instanceof AudioSharingDeviceVolumePreference).isTrue();
assertThat(((AudioSharingDeviceVolumePreference) captor.getValue()).getCachedDevice())
.isEqualTo(mCachedBluetoothDevice);
.isEqualTo(mCachedDevice);
}
@Test
public void onProfileConnectionStateChanged_leaDeviceConnected_noSource_removesPref() {
setupPreferenceMapWithDevice();
mDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice,
mCachedDevice,
BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.LE_AUDIO);
shadowOf(Looper.getMainLooper()).idle();
when(mAssistant.getAllSources(mBluetoothDevice)).thenReturn(ImmutableList.of());
when(mAssistant.getAllSources(mDevice)).thenReturn(ImmutableList.of());
ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class);
mDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice,
mCachedDevice,
BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.LE_AUDIO);
shadowOf(Looper.getMainLooper()).idle();
@@ -169,14 +170,14 @@ public class AudioSharingDeviceVolumeControlUpdaterTest {
verify(mDevicePreferenceCallback).onDeviceRemoved(captor.capture());
assertThat(captor.getValue() instanceof AudioSharingDeviceVolumePreference).isTrue();
assertThat(((AudioSharingDeviceVolumePreference) captor.getValue()).getCachedDevice())
.isEqualTo(mCachedBluetoothDevice);
.isEqualTo(mCachedDevice);
}
@Test
public void onProfileConnectionStateChanged_deviceIsNotInList_removesPref() {
setupPreferenceMapWithDevice();
mDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice,
mCachedDevice,
BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.LE_AUDIO);
shadowOf(Looper.getMainLooper()).idle();
@@ -186,7 +187,7 @@ public class AudioSharingDeviceVolumeControlUpdaterTest {
ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class);
mDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice,
mCachedDevice,
BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.LE_AUDIO);
shadowOf(Looper.getMainLooper()).idle();
@@ -194,14 +195,14 @@ public class AudioSharingDeviceVolumeControlUpdaterTest {
verify(mDevicePreferenceCallback).onDeviceRemoved(captor.capture());
assertThat(captor.getValue() instanceof AudioSharingDeviceVolumePreference).isTrue();
assertThat(((AudioSharingDeviceVolumePreference) captor.getValue()).getCachedDevice())
.isEqualTo(mCachedBluetoothDevice);
.isEqualTo(mCachedDevice);
}
@Test
public void onProfileConnectionStateChanged_leaDeviceDisconnected_removesPref() {
setupPreferenceMapWithDevice();
mDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice,
mCachedDevice,
BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.LE_AUDIO);
shadowOf(Looper.getMainLooper()).idle();
@@ -210,7 +211,7 @@ public class AudioSharingDeviceVolumeControlUpdaterTest {
ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class);
mDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice,
mCachedDevice,
BluetoothProfile.STATE_DISCONNECTED,
BluetoothProfile.LE_AUDIO);
shadowOf(Looper.getMainLooper()).idle();
@@ -218,23 +219,23 @@ public class AudioSharingDeviceVolumeControlUpdaterTest {
verify(mDevicePreferenceCallback).onDeviceRemoved(captor.capture());
assertThat(captor.getValue() instanceof AudioSharingDeviceVolumePreference).isTrue();
assertThat(((AudioSharingDeviceVolumePreference) captor.getValue()).getCachedDevice())
.isEqualTo(mCachedBluetoothDevice);
.isEqualTo(mCachedDevice);
}
@Test
public void onProfileConnectionStateChanged_leaDeviceDisconnecting_removesPref() {
setupPreferenceMapWithDevice();
mDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice,
mCachedDevice,
BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.LE_AUDIO);
shadowOf(Looper.getMainLooper()).idle();
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(false);
when(mCachedDevice.isConnectedLeAudioDevice()).thenReturn(false);
ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class);
mDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice,
mCachedDevice,
BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.LE_AUDIO);
shadowOf(Looper.getMainLooper()).idle();
@@ -242,7 +243,7 @@ public class AudioSharingDeviceVolumeControlUpdaterTest {
verify(mDevicePreferenceCallback).onDeviceRemoved(captor.capture());
assertThat(captor.getValue() instanceof AudioSharingDeviceVolumePreference).isTrue();
assertThat(((AudioSharingDeviceVolumePreference) captor.getValue()).getCachedDevice())
.isEqualTo(mCachedBluetoothDevice);
.isEqualTo(mCachedDevice);
}
@Test
@@ -250,7 +251,7 @@ public class AudioSharingDeviceVolumeControlUpdaterTest {
ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class);
setupPreferenceMapWithDevice();
mDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice,
mCachedDevice,
BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.LE_AUDIO);
shadowOf(Looper.getMainLooper()).idle();
@@ -258,17 +259,17 @@ public class AudioSharingDeviceVolumeControlUpdaterTest {
verify(mDevicePreferenceCallback).onDeviceAdded(captor.capture());
assertThat(captor.getValue() instanceof AudioSharingDeviceVolumePreference).isTrue();
assertThat(((AudioSharingDeviceVolumePreference) captor.getValue()).getCachedDevice())
.isEqualTo(mCachedBluetoothDevice);
.isEqualTo(mCachedDevice);
}
@Test
public void onProfileConnectionStateChanged_hasLeaMemberConnected_hasSource_addsPreference() {
ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class);
setupPreferenceMapWithDevice();
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(false);
when(mCachedBluetoothDevice.hasConnectedLeAudioMemberDevice()).thenReturn(true);
when(mCachedDevice.isConnectedLeAudioDevice()).thenReturn(false);
when(mCachedDevice.hasConnectedLeAudioMemberDevice()).thenReturn(true);
mDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice,
mCachedDevice,
BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.LE_AUDIO);
shadowOf(Looper.getMainLooper()).idle();
@@ -276,7 +277,7 @@ public class AudioSharingDeviceVolumeControlUpdaterTest {
verify(mDevicePreferenceCallback).onDeviceAdded(captor.capture());
assertThat(captor.getValue() instanceof AudioSharingDeviceVolumePreference).isTrue();
assertThat(((AudioSharingDeviceVolumePreference) captor.getValue()).getCachedDevice())
.isEqualTo(mCachedBluetoothDevice);
.isEqualTo(mCachedDevice);
}
@Test
@@ -292,7 +293,7 @@ public class AudioSharingDeviceVolumeControlUpdaterTest {
@Test
public void addPreferenceWithSortType_doNothing() {
mDeviceUpdater.addPreference(
mCachedBluetoothDevice, BluetoothDevicePreference.SortType.TYPE_DEFAULT);
mCachedDevice, BluetoothDevicePreference.SortType.TYPE_DEFAULT);
// Verify AudioSharingDeviceVolumeControlUpdater overrides BluetoothDeviceUpdater and won't
// trigger add preference.
verifyNoInteractions(mDevicePreferenceCallback);
@@ -308,27 +309,118 @@ public class AudioSharingDeviceVolumeControlUpdaterTest {
}
@Test
public void refreshPreference_doNothing() {
public void refreshPreference_havePreference_refresh() {
setupPreferenceMapWithDevice();
ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class);
mDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice,
mCachedDevice,
BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.LE_AUDIO);
shadowOf(Looper.getMainLooper()).idle();
verify(mDevicePreferenceCallback).onDeviceAdded(captor.capture());
assertThat(captor.getValue() instanceof AudioSharingDeviceVolumePreference).isTrue();
AudioSharingDeviceVolumePreference preference =
(AudioSharingDeviceVolumePreference) captor.getValue();
when(mCachedDevice.getName()).thenReturn("new");
mDeviceUpdater.refreshPreference();
shadowOf(Looper.getMainLooper()).idle();
assertThat(preference.getTitle().toString()).isEqualTo("new");
}
@Test
public void refreshPreference_staledPreference_remove() {
setupPreferenceMapWithDevice();
ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class);
mDeviceUpdater.onProfileConnectionStateChanged(
mCachedDevice,
BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.LE_AUDIO);
shadowOf(Looper.getMainLooper()).idle();
verify(mDevicePreferenceCallback).onDeviceAdded(captor.capture());
assertThat(captor.getValue() instanceof AudioSharingDeviceVolumePreference).isTrue();
AudioSharingDeviceVolumePreference preference =
(AudioSharingDeviceVolumePreference) captor.getValue();
verify(mDevicePreferenceCallback).onDeviceAdded(any(Preference.class));
when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(ImmutableList.of());
mDeviceUpdater.refreshPreference();
// Verify AudioSharingDeviceVolumeControlUpdater overrides BluetoothDeviceUpdater and won't
// refresh preference map
verify(mDevicePreferenceCallback, never()).onDeviceRemoved(any(Preference.class));
shadowOf(Looper.getMainLooper()).idle();
verify(mDevicePreferenceCallback).onDeviceRemoved(preference);
}
@Test
public void refreshPreference_inconsistentPreference_doNothing() {
setupPreferenceMapWithDevice();
ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class);
CachedBluetoothDevice subCachedDevice = mock(CachedBluetoothDevice.class);
BluetoothDevice subDevice = mock(BluetoothDevice.class);
when(subDevice.getAddress()).thenReturn("XX:XX:XX:XX:XX:22");
when(subCachedDevice.getDevice()).thenReturn(subDevice);
when(mCachedDevice.getMemberDevice()).thenReturn(ImmutableSet.of(subCachedDevice));
when(subCachedDevice.isConnectedLeAudioDevice()).thenReturn(true);
when(mAssistant.getAllSources(subDevice)).thenReturn(ImmutableList.of(mState));
mDeviceUpdater.onProfileConnectionStateChanged(
mCachedDevice,
BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.LE_AUDIO);
shadowOf(Looper.getMainLooper()).idle();
verify(mDevicePreferenceCallback).onDeviceAdded(captor.capture());
assertThat(captor.getValue() instanceof AudioSharingDeviceVolumePreference).isTrue();
AudioSharingDeviceVolumePreference preference =
(AudioSharingDeviceVolumePreference) captor.getValue();
// Main device disconnected, CSIP switches the content of main and member.
when(mCachedDevice.isConnectedLeAudioDevice()).thenReturn(false);
when(mCachedDevice.getDevice()).thenReturn(subDevice);
when(subCachedDevice.getDevice()).thenReturn(mDevice);
when(mCachedDevice.getMemberDevice()).thenReturn(ImmutableSet.of());
when(subCachedDevice.getMemberDevice()).thenReturn(ImmutableSet.of(subCachedDevice));
when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(
ImmutableList.of(subCachedDevice));
// New main device is added, its preference won't be added to the preference group because
// it equals to previous preference for the old main device.
mDeviceUpdater.onDeviceAdded(subCachedDevice);
shadowOf(Looper.getMainLooper()).idle();
mDeviceUpdater.refreshPreference();
shadowOf(Looper.getMainLooper()).idle();
// The preference should not be removed after refresh the preference group.
verify(mDevicePreferenceCallback, never()).onDeviceRemoved(preference);
}
@Test
public void refreshPreference_staledInconsistentPreference_remove() {
setupPreferenceMapWithDevice();
ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class);
mDeviceUpdater.onProfileConnectionStateChanged(
mCachedDevice,
BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.LE_AUDIO);
shadowOf(Looper.getMainLooper()).idle();
verify(mDevicePreferenceCallback).onDeviceAdded(captor.capture());
assertThat(captor.getValue() instanceof AudioSharingDeviceVolumePreference).isTrue();
AudioSharingDeviceVolumePreference preference =
(AudioSharingDeviceVolumePreference) captor.getValue();
BluetoothDevice subDevice = mock(BluetoothDevice.class);
when(subDevice.getAddress()).thenReturn("XX:XX:XX:XX:XX:22");
when(mCachedDevice.getDevice()).thenReturn(subDevice);
when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(ImmutableList.of());
mDeviceUpdater.refreshPreference();
verify(mDevicePreferenceCallback).onDeviceRemoved(preference);
}
private void setupPreferenceMapWithDevice() {
// Add device to preferenceMap
when(mBroadcast.isEnabled(null)).thenReturn(true);
when(mAssistant.getAllSources(mBluetoothDevice)).thenReturn(ImmutableList.of(mState));
when(mAssistant.getAllSources(mDevice)).thenReturn(ImmutableList.of(mState));
when(mDeviceUpdater.isDeviceConnected(any(CachedBluetoothDevice.class))).thenReturn(true);
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
when(mCachedDevice.isConnectedLeAudioDevice()).thenReturn(true);
}
}

View File

@@ -21,16 +21,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.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.media.AudioManager;
import android.os.Looper;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
@@ -44,12 +47,15 @@ import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowBluetoothUtils;
import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LeAudioProfile;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.settingslib.bluetooth.VolumeControlProfile;
import com.android.settingslib.flags.Flags;
import com.google.common.collect.ImmutableList;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -63,6 +69,8 @@ import org.robolectric.annotation.Config;
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowBluetoothUtils.class})
public class AudioSharingDeviceVolumePreferenceTest {
private static final String TEST_DEVICE_NAME = "test";
private static final String TEST_DEVICE_ADDRESS = "XX:XX:XX:XX:XX:11";
private static final int TEST_DEVICE_GROUP_ID = 1;
private static final int TEST_VOLUME_VALUE = 255;
private static final int TEST_MAX_STREAM_VALUE = 10;
@@ -74,6 +82,7 @@ public class AudioSharingDeviceVolumePreferenceTest {
@Mock private LocalBluetoothManager mLocalBtManager;
@Mock private LocalBluetoothProfileManager mLocalBtProfileManager;
@Mock private LeAudioProfile mLeAudioProfile;
@Mock private CachedBluetoothDeviceManager mDeviceManager;
@Mock private VolumeControlProfile mVolumeControl;
@Mock private CachedBluetoothDevice mCachedDevice;
@Mock private BluetoothDevice mDevice;
@@ -92,13 +101,17 @@ public class AudioSharingDeviceVolumePreferenceTest {
when(mLocalBtManager.getProfileManager()).thenReturn(mLocalBtProfileManager);
when(mLocalBtProfileManager.getVolumeControlProfile()).thenReturn(mVolumeControl);
when(mLocalBtProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile);
when(mLocalBtManager.getCachedDeviceManager()).thenReturn(mDeviceManager);
when(mContext.getSystemService(AudioManager.class)).thenReturn(mAudioManager);
when(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
.thenReturn(TEST_MAX_STREAM_VALUE);
when(mAudioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC))
.thenReturn(TEST_MIN_STREAM_VALUE);
when(mDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
when(mCachedDevice.getName()).thenReturn(TEST_DEVICE_NAME);
when(mCachedDevice.getDevice()).thenReturn(mDevice);
when(mCachedDevice.getGroupId()).thenReturn(TEST_DEVICE_GROUP_ID);
when(mDeviceManager.getCachedDevicesCopy()).thenReturn(ImmutableList.of(mCachedDevice));
when(mSeekBar.getProgress()).thenReturn(TEST_VOLUME_VALUE);
mPreference = new AudioSharingDeviceVolumePreference(mContext, mCachedDevice);
}
@@ -305,4 +318,39 @@ public class AudioSharingDeviceVolumePreferenceTest {
eq(SettingsEnums.ACTION_AUDIO_SHARING_CHANGE_MEDIA_DEVICE_VOLUME),
anyBoolean());
}
@Test
public void equals_returnsTrue() {
AudioSharingDeviceVolumePreference preference = new AudioSharingDeviceVolumePreference(
mContext, mCachedDevice);
assertThat(mPreference.equals(preference)).isTrue();
}
@Test
public void equals_returnsFalse() {
CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
AudioSharingDeviceVolumePreference preference = new AudioSharingDeviceVolumePreference(
mContext, cachedDevice);
assertThat(mPreference.equals(preference)).isFalse();
}
@Test
public void toString_correctValue() {
mPreference.setTitle(TEST_DEVICE_NAME);
StringBuilder builder = new StringBuilder("Preference{");
builder.append("preference=").append(TEST_DEVICE_NAME);
when(mDevice.getAnonymizedAddress()).thenReturn(TEST_DEVICE_ADDRESS);
builder.append(", device=").append(TEST_DEVICE_ADDRESS);
builder.append("}");
assertThat(mPreference.toString()).isEqualTo(builder.toString());
}
@Test
public void onPreferenceAttributesChanged_nameChanged_updatePreference() {
when(mCachedDevice.getName()).thenReturn("new");
mPreference.onPreferenceAttributesChanged();
shadowOf(Looper.getMainLooper()).idle();
assertThat(mPreference.getTitle().toString()).isEqualTo("new");
}
}