[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.containsKey(device)) {
if (mPreferenceMap.get(device) instanceof BluetoothDevicePreference preference) { if (mPreferenceMap.get(device) instanceof BluetoothDevicePreference preference) {
BluetoothDevice prefDevice = preference.getBluetoothDevice().getDevice(); BluetoothDevice prefDevice = preference.getBluetoothDevice().getDevice();
@@ -362,7 +365,7 @@ public abstract class BluetoothDeviceUpdater implements BluetoothCallback,
return mLocalManager.getCachedDeviceManager().getCachedDevicesCopy().contains(cachedDevice); return mLocalManager.getCachedDeviceManager().getCachedDevicesCopy().contains(cachedDevice);
} }
private boolean isDeviceOfMapInCachedDevicesList(BluetoothDevice inputBluetoothDevice) { protected boolean isDeviceOfMapInCachedDevicesList(BluetoothDevice inputBluetoothDevice) {
Collection<CachedBluetoothDevice> cachedDevices = Collection<CachedBluetoothDevice> cachedDevices =
mLocalManager.getCachedDeviceManager().getCachedDevicesCopy(); mLocalManager.getCachedDeviceManager().getCachedDevicesCopy();
if (cachedDevices == null || cachedDevices.isEmpty()) { 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.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.VolumeControlProfile;
public class AudioSharingDeviceVolumeControlUpdater extends BluetoothDeviceUpdater public class AudioSharingDeviceVolumeControlUpdater extends BluetoothDeviceUpdater
implements Preference.OnPreferenceClickListener { implements Preference.OnPreferenceClickListener {
@@ -42,7 +41,6 @@ public class AudioSharingDeviceVolumeControlUpdater extends BluetoothDeviceUpdat
static final String PREF_KEY_PREFIX = "audio_sharing_volume_control_"; static final String PREF_KEY_PREFIX = "audio_sharing_volume_control_";
@Nullable private final LocalBluetoothManager mBtManager; @Nullable private final LocalBluetoothManager mBtManager;
@Nullable private final VolumeControlProfile mVolumeControl;
public AudioSharingDeviceVolumeControlUpdater( public AudioSharingDeviceVolumeControlUpdater(
Context context, Context context,
@@ -50,10 +48,6 @@ public class AudioSharingDeviceVolumeControlUpdater extends BluetoothDeviceUpdat
int metricsCategory) { int metricsCategory) {
super(context, devicePreferenceCallback, metricsCategory); super(context, devicePreferenceCallback, metricsCategory);
mBtManager = Utils.getLocalBluetoothManager(context); mBtManager = Utils.getLocalBluetoothManager(context);
mVolumeControl =
mBtManager == null
? null
: mBtManager.getProfileManager().getVolumeControlProfile();
} }
@Override @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 @Override
protected String getPreferenceKeyPrefix() { protected String getPreferenceKeyPrefix() {
return PREF_KEY_PREFIX; return PREF_KEY_PREFIX;
@@ -121,7 +149,4 @@ public class AudioSharingDeviceVolumeControlUpdater extends BluetoothDeviceUpdat
@Override @Override
protected void launchDeviceDetails(Preference preference) {} protected void launchDeviceDetails(Preference preference) {}
@Override
public void refreshPreference() {}
} }

View File

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

View File

@@ -104,6 +104,39 @@ public class AudioSharingDeviceVolumePreference extends SeekBarPreference {
handleProgressChange(seekBar.getProgress()); 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) { private void handleProgressChange(int progress) {
var unused = var unused =
ThreadUtils.postOnBackgroundThread( ThreadUtils.postOnBackgroundThread(

View File

@@ -518,6 +518,7 @@ public class AvailableMediaBluetoothDeviceUpdaterTest {
mContext, mDevicePreferenceCallback, /* metricsCategory= */ 0)); mContext, mDevicePreferenceCallback, /* metricsCategory= */ 0));
mBluetoothDeviceUpdater.setPrefContext(mContext); mBluetoothDeviceUpdater.setPrefContext(mContext);
doNothing().when(mBluetoothDeviceUpdater).addPreference(any()); 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)); mDevicePreferenceCallback, /* metricsCategory= */ 0));
mBluetoothDeviceUpdater.setPrefContext(mContext); mBluetoothDeviceUpdater.setPrefContext(mContext);
doNothing().when(mBluetoothDeviceUpdater).addPreference(any()); 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, mPreference = new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
false, BluetoothDevicePreference.SortType.TYPE_DEFAULT); false, BluetoothDevicePreference.SortType.TYPE_DEFAULT);
doNothing().when(mBluetoothDeviceUpdater).addPreference(any()); doNothing().when(mBluetoothDeviceUpdater).addPreference(any());
doNothing().when(mBluetoothDeviceUpdater).removePreference(any()); doNothing().when(mBluetoothDeviceUpdater).removePreference(
any(CachedBluetoothDevice.class));
mCachedDevices.add(mCachedBluetoothDevice); mCachedDevices.add(mCachedBluetoothDevice);
when(mBluetoothManager.getCachedDeviceManager()).thenReturn(mDeviceManager); when(mBluetoothManager.getCachedDeviceManager()).thenReturn(mDeviceManager);
when(mDeviceManager.getCachedDevicesCopy()).thenReturn(mCachedDevices); 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.ArgumentMatchers.any;
import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
@@ -74,13 +73,14 @@ import java.util.List;
@Config(shadows = {ShadowBluetoothUtils.class}) @Config(shadows = {ShadowBluetoothUtils.class})
public class AudioSharingDeviceVolumeControlUpdaterTest { public class AudioSharingDeviceVolumeControlUpdaterTest {
private static final String TEST_DEVICE_NAME = "test"; 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"; private static final String TAG = "AudioSharingVolUpdater";
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule(); @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@Mock private DevicePreferenceCallback mDevicePreferenceCallback; @Mock private DevicePreferenceCallback mDevicePreferenceCallback;
@Mock private CachedBluetoothDevice mCachedBluetoothDevice; @Mock private CachedBluetoothDevice mCachedDevice;
@Mock private BluetoothDevice mBluetoothDevice; @Mock private BluetoothDevice mDevice;
@Mock private LocalBluetoothManager mLocalBtManager; @Mock private LocalBluetoothManager mLocalBtManager;
@Mock private CachedBluetoothDeviceManager mCachedDeviceManager; @Mock private CachedBluetoothDeviceManager mCachedDeviceManager;
@Mock private LocalBluetoothProfileManager mLocalBtProfileManager; @Mock private LocalBluetoothProfileManager mLocalBtProfileManager;
@@ -104,11 +104,12 @@ public class AudioSharingDeviceVolumeControlUpdaterTest {
List<Long> bisSyncState = new ArrayList<>(); List<Long> bisSyncState = new ArrayList<>();
bisSyncState.add(1L); bisSyncState.add(1L);
when(mState.getBisSyncState()).thenReturn(bisSyncState); when(mState.getBisSyncState()).thenReturn(bisSyncState);
doReturn(TEST_DEVICE_NAME).when(mCachedBluetoothDevice).getName(); when(mDevice.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
doReturn(mBluetoothDevice).when(mCachedBluetoothDevice).getDevice(); when(mCachedDevice.getName()).thenReturn(TEST_DEVICE_NAME);
doReturn(ImmutableSet.of()).when(mCachedBluetoothDevice).getMemberDevice(); when(mCachedDevice.getDevice()).thenReturn(mDevice);
when(mCachedDevice.getMemberDevice()).thenReturn(ImmutableSet.of());
mCachedDevices = new ArrayList<>(); mCachedDevices = new ArrayList<>();
mCachedDevices.add(mCachedBluetoothDevice); mCachedDevices.add(mCachedDevice);
when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(mCachedDevices); when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(mCachedDevices);
doNothing().when(mDevicePreferenceCallback).onDeviceAdded(any(Preference.class)); doNothing().when(mDevicePreferenceCallback).onDeviceAdded(any(Preference.class));
doNothing().when(mDevicePreferenceCallback).onDeviceRemoved(any(Preference.class)); doNothing().when(mDevicePreferenceCallback).onDeviceRemoved(any(Preference.class));
@@ -128,7 +129,7 @@ public class AudioSharingDeviceVolumeControlUpdaterTest {
public void onProfileConnectionStateChanged_leaDeviceConnected_noSharing_removesPref() { public void onProfileConnectionStateChanged_leaDeviceConnected_noSharing_removesPref() {
setupPreferenceMapWithDevice(); setupPreferenceMapWithDevice();
mDeviceUpdater.onProfileConnectionStateChanged( mDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice, mCachedDevice,
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.LE_AUDIO); BluetoothProfile.LE_AUDIO);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
@@ -137,7 +138,7 @@ public class AudioSharingDeviceVolumeControlUpdaterTest {
ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class); ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class);
mDeviceUpdater.onProfileConnectionStateChanged( mDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice, mCachedDevice,
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.LE_AUDIO); BluetoothProfile.LE_AUDIO);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
@@ -145,23 +146,23 @@ public class AudioSharingDeviceVolumeControlUpdaterTest {
verify(mDevicePreferenceCallback).onDeviceRemoved(captor.capture()); verify(mDevicePreferenceCallback).onDeviceRemoved(captor.capture());
assertThat(captor.getValue() instanceof AudioSharingDeviceVolumePreference).isTrue(); assertThat(captor.getValue() instanceof AudioSharingDeviceVolumePreference).isTrue();
assertThat(((AudioSharingDeviceVolumePreference) captor.getValue()).getCachedDevice()) assertThat(((AudioSharingDeviceVolumePreference) captor.getValue()).getCachedDevice())
.isEqualTo(mCachedBluetoothDevice); .isEqualTo(mCachedDevice);
} }
@Test @Test
public void onProfileConnectionStateChanged_leaDeviceConnected_noSource_removesPref() { public void onProfileConnectionStateChanged_leaDeviceConnected_noSource_removesPref() {
setupPreferenceMapWithDevice(); setupPreferenceMapWithDevice();
mDeviceUpdater.onProfileConnectionStateChanged( mDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice, mCachedDevice,
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.LE_AUDIO); BluetoothProfile.LE_AUDIO);
shadowOf(Looper.getMainLooper()).idle(); 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); ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class);
mDeviceUpdater.onProfileConnectionStateChanged( mDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice, mCachedDevice,
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.LE_AUDIO); BluetoothProfile.LE_AUDIO);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
@@ -169,14 +170,14 @@ public class AudioSharingDeviceVolumeControlUpdaterTest {
verify(mDevicePreferenceCallback).onDeviceRemoved(captor.capture()); verify(mDevicePreferenceCallback).onDeviceRemoved(captor.capture());
assertThat(captor.getValue() instanceof AudioSharingDeviceVolumePreference).isTrue(); assertThat(captor.getValue() instanceof AudioSharingDeviceVolumePreference).isTrue();
assertThat(((AudioSharingDeviceVolumePreference) captor.getValue()).getCachedDevice()) assertThat(((AudioSharingDeviceVolumePreference) captor.getValue()).getCachedDevice())
.isEqualTo(mCachedBluetoothDevice); .isEqualTo(mCachedDevice);
} }
@Test @Test
public void onProfileConnectionStateChanged_deviceIsNotInList_removesPref() { public void onProfileConnectionStateChanged_deviceIsNotInList_removesPref() {
setupPreferenceMapWithDevice(); setupPreferenceMapWithDevice();
mDeviceUpdater.onProfileConnectionStateChanged( mDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice, mCachedDevice,
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.LE_AUDIO); BluetoothProfile.LE_AUDIO);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
@@ -186,7 +187,7 @@ public class AudioSharingDeviceVolumeControlUpdaterTest {
ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class); ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class);
mDeviceUpdater.onProfileConnectionStateChanged( mDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice, mCachedDevice,
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.LE_AUDIO); BluetoothProfile.LE_AUDIO);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
@@ -194,14 +195,14 @@ public class AudioSharingDeviceVolumeControlUpdaterTest {
verify(mDevicePreferenceCallback).onDeviceRemoved(captor.capture()); verify(mDevicePreferenceCallback).onDeviceRemoved(captor.capture());
assertThat(captor.getValue() instanceof AudioSharingDeviceVolumePreference).isTrue(); assertThat(captor.getValue() instanceof AudioSharingDeviceVolumePreference).isTrue();
assertThat(((AudioSharingDeviceVolumePreference) captor.getValue()).getCachedDevice()) assertThat(((AudioSharingDeviceVolumePreference) captor.getValue()).getCachedDevice())
.isEqualTo(mCachedBluetoothDevice); .isEqualTo(mCachedDevice);
} }
@Test @Test
public void onProfileConnectionStateChanged_leaDeviceDisconnected_removesPref() { public void onProfileConnectionStateChanged_leaDeviceDisconnected_removesPref() {
setupPreferenceMapWithDevice(); setupPreferenceMapWithDevice();
mDeviceUpdater.onProfileConnectionStateChanged( mDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice, mCachedDevice,
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.LE_AUDIO); BluetoothProfile.LE_AUDIO);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
@@ -210,7 +211,7 @@ public class AudioSharingDeviceVolumeControlUpdaterTest {
ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class); ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class);
mDeviceUpdater.onProfileConnectionStateChanged( mDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice, mCachedDevice,
BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.STATE_DISCONNECTED,
BluetoothProfile.LE_AUDIO); BluetoothProfile.LE_AUDIO);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
@@ -218,23 +219,23 @@ public class AudioSharingDeviceVolumeControlUpdaterTest {
verify(mDevicePreferenceCallback).onDeviceRemoved(captor.capture()); verify(mDevicePreferenceCallback).onDeviceRemoved(captor.capture());
assertThat(captor.getValue() instanceof AudioSharingDeviceVolumePreference).isTrue(); assertThat(captor.getValue() instanceof AudioSharingDeviceVolumePreference).isTrue();
assertThat(((AudioSharingDeviceVolumePreference) captor.getValue()).getCachedDevice()) assertThat(((AudioSharingDeviceVolumePreference) captor.getValue()).getCachedDevice())
.isEqualTo(mCachedBluetoothDevice); .isEqualTo(mCachedDevice);
} }
@Test @Test
public void onProfileConnectionStateChanged_leaDeviceDisconnecting_removesPref() { public void onProfileConnectionStateChanged_leaDeviceDisconnecting_removesPref() {
setupPreferenceMapWithDevice(); setupPreferenceMapWithDevice();
mDeviceUpdater.onProfileConnectionStateChanged( mDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice, mCachedDevice,
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.LE_AUDIO); BluetoothProfile.LE_AUDIO);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(false); when(mCachedDevice.isConnectedLeAudioDevice()).thenReturn(false);
ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class); ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class);
mDeviceUpdater.onProfileConnectionStateChanged( mDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice, mCachedDevice,
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.LE_AUDIO); BluetoothProfile.LE_AUDIO);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
@@ -242,7 +243,7 @@ public class AudioSharingDeviceVolumeControlUpdaterTest {
verify(mDevicePreferenceCallback).onDeviceRemoved(captor.capture()); verify(mDevicePreferenceCallback).onDeviceRemoved(captor.capture());
assertThat(captor.getValue() instanceof AudioSharingDeviceVolumePreference).isTrue(); assertThat(captor.getValue() instanceof AudioSharingDeviceVolumePreference).isTrue();
assertThat(((AudioSharingDeviceVolumePreference) captor.getValue()).getCachedDevice()) assertThat(((AudioSharingDeviceVolumePreference) captor.getValue()).getCachedDevice())
.isEqualTo(mCachedBluetoothDevice); .isEqualTo(mCachedDevice);
} }
@Test @Test
@@ -250,7 +251,7 @@ public class AudioSharingDeviceVolumeControlUpdaterTest {
ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class); ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class);
setupPreferenceMapWithDevice(); setupPreferenceMapWithDevice();
mDeviceUpdater.onProfileConnectionStateChanged( mDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice, mCachedDevice,
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.LE_AUDIO); BluetoothProfile.LE_AUDIO);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
@@ -258,17 +259,17 @@ public class AudioSharingDeviceVolumeControlUpdaterTest {
verify(mDevicePreferenceCallback).onDeviceAdded(captor.capture()); verify(mDevicePreferenceCallback).onDeviceAdded(captor.capture());
assertThat(captor.getValue() instanceof AudioSharingDeviceVolumePreference).isTrue(); assertThat(captor.getValue() instanceof AudioSharingDeviceVolumePreference).isTrue();
assertThat(((AudioSharingDeviceVolumePreference) captor.getValue()).getCachedDevice()) assertThat(((AudioSharingDeviceVolumePreference) captor.getValue()).getCachedDevice())
.isEqualTo(mCachedBluetoothDevice); .isEqualTo(mCachedDevice);
} }
@Test @Test
public void onProfileConnectionStateChanged_hasLeaMemberConnected_hasSource_addsPreference() { public void onProfileConnectionStateChanged_hasLeaMemberConnected_hasSource_addsPreference() {
ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class); ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class);
setupPreferenceMapWithDevice(); setupPreferenceMapWithDevice();
when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(false); when(mCachedDevice.isConnectedLeAudioDevice()).thenReturn(false);
when(mCachedBluetoothDevice.hasConnectedLeAudioMemberDevice()).thenReturn(true); when(mCachedDevice.hasConnectedLeAudioMemberDevice()).thenReturn(true);
mDeviceUpdater.onProfileConnectionStateChanged( mDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice, mCachedDevice,
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.LE_AUDIO); BluetoothProfile.LE_AUDIO);
shadowOf(Looper.getMainLooper()).idle(); shadowOf(Looper.getMainLooper()).idle();
@@ -276,7 +277,7 @@ public class AudioSharingDeviceVolumeControlUpdaterTest {
verify(mDevicePreferenceCallback).onDeviceAdded(captor.capture()); verify(mDevicePreferenceCallback).onDeviceAdded(captor.capture());
assertThat(captor.getValue() instanceof AudioSharingDeviceVolumePreference).isTrue(); assertThat(captor.getValue() instanceof AudioSharingDeviceVolumePreference).isTrue();
assertThat(((AudioSharingDeviceVolumePreference) captor.getValue()).getCachedDevice()) assertThat(((AudioSharingDeviceVolumePreference) captor.getValue()).getCachedDevice())
.isEqualTo(mCachedBluetoothDevice); .isEqualTo(mCachedDevice);
} }
@Test @Test
@@ -292,7 +293,7 @@ public class AudioSharingDeviceVolumeControlUpdaterTest {
@Test @Test
public void addPreferenceWithSortType_doNothing() { public void addPreferenceWithSortType_doNothing() {
mDeviceUpdater.addPreference( mDeviceUpdater.addPreference(
mCachedBluetoothDevice, BluetoothDevicePreference.SortType.TYPE_DEFAULT); mCachedDevice, BluetoothDevicePreference.SortType.TYPE_DEFAULT);
// Verify AudioSharingDeviceVolumeControlUpdater overrides BluetoothDeviceUpdater and won't // Verify AudioSharingDeviceVolumeControlUpdater overrides BluetoothDeviceUpdater and won't
// trigger add preference. // trigger add preference.
verifyNoInteractions(mDevicePreferenceCallback); verifyNoInteractions(mDevicePreferenceCallback);
@@ -308,27 +309,118 @@ public class AudioSharingDeviceVolumeControlUpdaterTest {
} }
@Test @Test
public void refreshPreference_doNothing() { public void refreshPreference_havePreference_refresh() {
setupPreferenceMapWithDevice(); setupPreferenceMapWithDevice();
ArgumentCaptor<Preference> captor = ArgumentCaptor.forClass(Preference.class);
mDeviceUpdater.onProfileConnectionStateChanged( mDeviceUpdater.onProfileConnectionStateChanged(
mCachedBluetoothDevice, mCachedDevice,
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_CONNECTED,
BluetoothProfile.LE_AUDIO); BluetoothProfile.LE_AUDIO);
shadowOf(Looper.getMainLooper()).idle(); 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()); when(mCachedDeviceManager.getCachedDevicesCopy()).thenReturn(ImmutableList.of());
mDeviceUpdater.refreshPreference(); mDeviceUpdater.refreshPreference();
// Verify AudioSharingDeviceVolumeControlUpdater overrides BluetoothDeviceUpdater and won't shadowOf(Looper.getMainLooper()).idle();
// refresh preference map
verify(mDevicePreferenceCallback, never()).onDeviceRemoved(any(Preference.class)); 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() { private void setupPreferenceMapWithDevice() {
// Add device to preferenceMap // Add device to preferenceMap
when(mBroadcast.isEnabled(null)).thenReturn(true); 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(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.any;
import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
import android.app.settings.SettingsEnums; import android.app.settings.SettingsEnums;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.content.Context; import android.content.Context;
import android.media.AudioManager; import android.media.AudioManager;
import android.os.Looper;
import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule; 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.settings.testutils.shadow.ShadowBluetoothUtils;
import com.android.settingslib.bluetooth.BluetoothUtils; import com.android.settingslib.bluetooth.BluetoothUtils;
import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.CachedBluetoothDevice;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LeAudioProfile; import com.android.settingslib.bluetooth.LeAudioProfile;
import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.settingslib.bluetooth.VolumeControlProfile; import com.android.settingslib.bluetooth.VolumeControlProfile;
import com.android.settingslib.flags.Flags; import com.android.settingslib.flags.Flags;
import com.google.common.collect.ImmutableList;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
@@ -63,6 +69,8 @@ import org.robolectric.annotation.Config;
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowBluetoothUtils.class}) @Config(shadows = {ShadowBluetoothUtils.class})
public class AudioSharingDeviceVolumePreferenceTest { 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_DEVICE_GROUP_ID = 1;
private static final int TEST_VOLUME_VALUE = 255; private static final int TEST_VOLUME_VALUE = 255;
private static final int TEST_MAX_STREAM_VALUE = 10; private static final int TEST_MAX_STREAM_VALUE = 10;
@@ -74,6 +82,7 @@ public class AudioSharingDeviceVolumePreferenceTest {
@Mock private LocalBluetoothManager mLocalBtManager; @Mock private LocalBluetoothManager mLocalBtManager;
@Mock private LocalBluetoothProfileManager mLocalBtProfileManager; @Mock private LocalBluetoothProfileManager mLocalBtProfileManager;
@Mock private LeAudioProfile mLeAudioProfile; @Mock private LeAudioProfile mLeAudioProfile;
@Mock private CachedBluetoothDeviceManager mDeviceManager;
@Mock private VolumeControlProfile mVolumeControl; @Mock private VolumeControlProfile mVolumeControl;
@Mock private CachedBluetoothDevice mCachedDevice; @Mock private CachedBluetoothDevice mCachedDevice;
@Mock private BluetoothDevice mDevice; @Mock private BluetoothDevice mDevice;
@@ -92,13 +101,17 @@ public class AudioSharingDeviceVolumePreferenceTest {
when(mLocalBtManager.getProfileManager()).thenReturn(mLocalBtProfileManager); when(mLocalBtManager.getProfileManager()).thenReturn(mLocalBtProfileManager);
when(mLocalBtProfileManager.getVolumeControlProfile()).thenReturn(mVolumeControl); when(mLocalBtProfileManager.getVolumeControlProfile()).thenReturn(mVolumeControl);
when(mLocalBtProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile); when(mLocalBtProfileManager.getLeAudioProfile()).thenReturn(mLeAudioProfile);
when(mLocalBtManager.getCachedDeviceManager()).thenReturn(mDeviceManager);
when(mContext.getSystemService(AudioManager.class)).thenReturn(mAudioManager); when(mContext.getSystemService(AudioManager.class)).thenReturn(mAudioManager);
when(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC)) when(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
.thenReturn(TEST_MAX_STREAM_VALUE); .thenReturn(TEST_MAX_STREAM_VALUE);
when(mAudioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC)) when(mAudioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC))
.thenReturn(TEST_MIN_STREAM_VALUE); .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.getDevice()).thenReturn(mDevice);
when(mCachedDevice.getGroupId()).thenReturn(TEST_DEVICE_GROUP_ID); when(mCachedDevice.getGroupId()).thenReturn(TEST_DEVICE_GROUP_ID);
when(mDeviceManager.getCachedDevicesCopy()).thenReturn(ImmutableList.of(mCachedDevice));
when(mSeekBar.getProgress()).thenReturn(TEST_VOLUME_VALUE); when(mSeekBar.getProgress()).thenReturn(TEST_VOLUME_VALUE);
mPreference = new AudioSharingDeviceVolumePreference(mContext, mCachedDevice); mPreference = new AudioSharingDeviceVolumePreference(mContext, mCachedDevice);
} }
@@ -305,4 +318,39 @@ public class AudioSharingDeviceVolumePreferenceTest {
eq(SettingsEnums.ACTION_AUDIO_SHARING_CHANGE_MEDIA_DEVICE_VOLUME), eq(SettingsEnums.ACTION_AUDIO_SHARING_CHANGE_MEDIA_DEVICE_VOLUME),
anyBoolean()); 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");
}
} }