diff --git a/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java b/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java index 6cbb9c20b79..ca1138c9b02 100644 --- a/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java +++ b/src/com/android/settings/bluetooth/BluetoothDeviceUpdater.java @@ -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 cachedDevices = mLocalManager.getCachedDeviceManager().getCachedDevicesCopy(); if (cachedDevices == null || cachedDevices.isEmpty()) { diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeControlUpdater.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeControlUpdater.java index 7f12ec84b3b..ff2b2e387a2 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeControlUpdater.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeControlUpdater.java @@ -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() {} } diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeGroupController.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeGroupController.java index 1659d2da2da..af860d13d27 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeGroupController.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeGroupController.java @@ -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, diff --git a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumePreference.java b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumePreference.java index 944ac25941b..aebd2069ded 100644 --- a/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumePreference.java +++ b/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumePreference.java @@ -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( diff --git a/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java index 87d7aed8f19..9c69e62c3a2 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/AvailableMediaBluetoothDeviceUpdaterTest.java @@ -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)); } } diff --git a/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java index 720b1d520d3..83f30147c60 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/ConnectedBluetoothDeviceUpdaterTest.java @@ -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)); } } diff --git a/tests/robotests/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdaterTest.java index 33fddb5efe7..557a5ecf8ff 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdaterTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdaterTest.java @@ -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); diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeControlUpdaterTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeControlUpdaterTest.java index 2bdd0da243f..1fa5cde3d60 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeControlUpdaterTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumeControlUpdaterTest.java @@ -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 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 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 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 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 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 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 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 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 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 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 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 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); } } diff --git a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumePreferenceTest.java b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumePreferenceTest.java index c84d7f8184d..f42c59e6e06 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumePreferenceTest.java +++ b/tests/robotests/src/com/android/settings/connecteddevice/audiosharing/AudioSharingDeviceVolumePreferenceTest.java @@ -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"); + } }