From 15c6533ef9de5b730fda33be3d27e8c7bbb14551 Mon Sep 17 00:00:00 2001 From: jasonwshsu Date: Fri, 19 Jul 2024 19:16:11 +0800 Subject: [PATCH] Connected devices page did not show correct summary when member device connect Root Cause: CsipDeviceManager only refreshes UI when switching member device content. Solution: * CsipDeviceManager needs to call refresh() on main device when it's new member device added. * UI widget Settings/BluetoothDevice also need to monitor it's member device status to refresh UI. Bug: 344947362 Test: atest BluetoothDevicePreferenceTest Flag: EXEMPT bugfix Change-Id: I58f9e2fc209d4e87631784d0538b1647228f4c1a --- .../BluetoothDetailsProfilesController.java | 18 +- .../bluetooth/BluetoothDevicePreference.java | 50 ++++-- ...AudioBluetoothDetailsHeaderController.java | 22 +-- src/com/android/settings/bluetooth/Utils.java | 19 +- .../BluetoothDevicePreferenceTest.java | 169 ++++++++++++++---- 5 files changed, 204 insertions(+), 74 deletions(-) diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java index 2b746842ad6..0897a4379b5 100644 --- a/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java +++ b/src/com/android/settings/bluetooth/BluetoothDetailsProfilesController.java @@ -89,7 +89,7 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll private LocalBluetoothManager mManager; private LocalBluetoothProfileManager mProfileManager; private CachedBluetoothDevice mCachedDevice; - private List mAllOfCachedDevices; + private Set mCachedDeviceGroup; private Map> mProfileDeviceMap = new HashMap>(); private boolean mIsLeContactSharingEnabled = false; @@ -105,7 +105,7 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll mManager = manager; mProfileManager = mManager.getProfileManager(); mCachedDevice = device; - mAllOfCachedDevices = Utils.getAllOfCachedBluetoothDevices(mManager, mCachedDevice); + mCachedDeviceGroup = Utils.findAllCachedBluetoothDevicesByGroupId(mManager, mCachedDevice); } @Override @@ -310,10 +310,10 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll private List getProfiles() { List result = new ArrayList<>(); mProfileDeviceMap.clear(); - if (mAllOfCachedDevices == null || mAllOfCachedDevices.isEmpty()) { + if (mCachedDeviceGroup == null || mCachedDeviceGroup.isEmpty()) { return result; } - for (CachedBluetoothDevice cachedItem : mAllOfCachedDevices) { + for (CachedBluetoothDevice cachedItem : mCachedDeviceGroup) { List tmpResult = cachedItem.getUiAccessibleProfiles(); for (LocalBluetoothProfile profile : tmpResult) { if (mProfileDeviceMap.containsKey(profile.toString())) { @@ -514,7 +514,7 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll @Override public void onPause() { - for (CachedBluetoothDevice item : mAllOfCachedDevices) { + for (CachedBluetoothDevice item : mCachedDeviceGroup) { item.unregisterCallback(this); } mProfileManager.removeServiceListener(this); @@ -523,7 +523,7 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll @Override public void onResume() { updateLeAudioConfig(); - for (CachedBluetoothDevice item : mAllOfCachedDevices) { + for (CachedBluetoothDevice item : mCachedDeviceGroup) { item.registerCallback(this); } mProfileManager.addServiceListener(this); @@ -545,11 +545,11 @@ public class BluetoothDetailsProfilesController extends BluetoothDetailsControll @Override public void onDeviceAttributesChanged() { - for (CachedBluetoothDevice item : mAllOfCachedDevices) { + for (CachedBluetoothDevice item : mCachedDeviceGroup) { item.unregisterCallback(this); } - mAllOfCachedDevices = Utils.getAllOfCachedBluetoothDevices(mManager, mCachedDevice); - for (CachedBluetoothDevice item : mAllOfCachedDevices) { + mCachedDeviceGroup = Utils.findAllCachedBluetoothDevicesByGroupId(mManager, mCachedDevice); + for (CachedBluetoothDevice item : mCachedDeviceGroup) { item.registerCallback(this); } diff --git a/src/com/android/settings/bluetooth/BluetoothDevicePreference.java b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java index ac0c63bc6de..209c900927e 100644 --- a/src/com/android/settings/bluetooth/BluetoothDevicePreference.java +++ b/src/com/android/settings/bluetooth/BluetoothDevicePreference.java @@ -46,6 +46,7 @@ import com.android.settings.R; import com.android.settings.overlay.FeatureFactory; import com.android.settings.widget.GearPreference; import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; import com.android.settingslib.utils.ThreadUtils; @@ -55,6 +56,7 @@ import java.util.HashSet; import java.util.Set; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; /** * BluetoothDevicePreference is the preference type used to display each remote @@ -76,7 +78,10 @@ public final class BluetoothDevicePreference extends GearPreference { } private final CachedBluetoothDevice mCachedDevice; + private Set mCachedDeviceGroup; + private final UserManager mUserManager; + private final LocalBluetoothManager mLocalBtManager; private Set mBluetoothDevices; @VisibleForTesting @@ -113,6 +118,21 @@ public final class BluetoothDevicePreference extends GearPreference { @Override public void onDeviceAttributesChanged() { onPreferenceAttributesChanged(); + Set newCachedDeviceGroup = new HashSet<>( + Utils.findAllCachedBluetoothDevicesByGroupId(mLocalBtManager, mCachedDevice)); + if (!mCachedDeviceGroup.equals(newCachedDeviceGroup)) { + for (CachedBluetoothDevice cachedBluetoothDevice : mCachedDeviceGroup) { + cachedBluetoothDevice.unregisterCallback(this); + } + unregisterMetadataChangedListener(); + + mCachedDeviceGroup = newCachedDeviceGroup; + + for (CachedBluetoothDevice cachedBluetoothDevice : mCachedDeviceGroup) { + cachedBluetoothDevice.registerCallback(getContext().getMainExecutor(), this); + } + registerMetadataChangedListener(); + } } } @@ -121,6 +141,7 @@ public final class BluetoothDevicePreference extends GearPreference { super(context, null); mResources = getContext().getResources(); mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); + mLocalBtManager = Utils.getLocalBluetoothManager(context); mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); mShowDevicesWithoutNames = showDeviceWithoutNames; @@ -131,6 +152,8 @@ public final class BluetoothDevicePreference extends GearPreference { } mCachedDevice = cachedDevice; + mCachedDeviceGroup = new HashSet<>( + Utils.findAllCachedBluetoothDevicesByGroupId(mLocalBtManager, mCachedDevice)); mCallback = new BluetoothDevicePreferenceCallback(); mId = sNextId.getAndIncrement(); mType = type; @@ -164,7 +187,9 @@ public final class BluetoothDevicePreference extends GearPreference { protected void onPrepareForRemoval() { super.onPrepareForRemoval(); if (!mIsCallbackRemoved) { - mCachedDevice.unregisterCallback(mCallback); + for (CachedBluetoothDevice cachedBluetoothDevice : mCachedDeviceGroup) { + cachedBluetoothDevice.unregisterCallback(mCallback); + } unregisterMetadataChangedListener(); mIsCallbackRemoved = true; } @@ -178,7 +203,9 @@ public final class BluetoothDevicePreference extends GearPreference { public void onAttached() { super.onAttached(); if (mIsCallbackRemoved) { - mCachedDevice.registerCallback(mCallback); + for (CachedBluetoothDevice cachedBluetoothDevice : mCachedDeviceGroup) { + cachedBluetoothDevice.registerCallback(getContext().getMainExecutor(), mCallback); + } registerMetadataChangedListener(); mIsCallbackRemoved = false; } @@ -189,7 +216,9 @@ public final class BluetoothDevicePreference extends GearPreference { public void onDetached() { super.onDetached(); if (!mIsCallbackRemoved) { - mCachedDevice.unregisterCallback(mCallback); + for (CachedBluetoothDevice cachedBluetoothDevice : mCachedDeviceGroup) { + cachedBluetoothDevice.unregisterCallback(mCallback); + } unregisterMetadataChangedListener(); mIsCallbackRemoved = true; } @@ -200,16 +229,11 @@ public final class BluetoothDevicePreference extends GearPreference { Log.d(TAG, "No mBluetoothAdapter"); return; } - if (mBluetoothDevices == null) { - mBluetoothDevices = new HashSet<>(); - } - mBluetoothDevices.clear(); - if (mCachedDevice.getDevice() != null) { - mBluetoothDevices.add(mCachedDevice.getDevice()); - } - for (CachedBluetoothDevice cbd : mCachedDevice.getMemberDevice()) { - mBluetoothDevices.add(cbd.getDevice()); - } + + mBluetoothDevices = mCachedDeviceGroup.stream() + .map(CachedBluetoothDevice::getDevice) + .collect(Collectors.toCollection(HashSet::new)); + if (mBluetoothDevices.isEmpty()) { Log.d(TAG, "No BT device to register."); return; diff --git a/src/com/android/settings/bluetooth/LeAudioBluetoothDetailsHeaderController.java b/src/com/android/settings/bluetooth/LeAudioBluetoothDetailsHeaderController.java index 4be4d63d7c3..a5e9cde2d17 100644 --- a/src/com/android/settings/bluetooth/LeAudioBluetoothDetailsHeaderController.java +++ b/src/com/android/settings/bluetooth/LeAudioBluetoothDetailsHeaderController.java @@ -47,7 +47,7 @@ import com.android.settingslib.core.lifecycle.events.OnStart; import com.android.settingslib.core.lifecycle.events.OnStop; import com.android.settingslib.widget.LayoutPreference; -import java.util.List; +import java.util.Set; /** * This class adds a header with device name and status (connected/disconnected, etc.). @@ -90,7 +90,7 @@ public class LeAudioBluetoothDetailsHeaderController extends BasePreferenceContr LayoutPreference mLayoutPreference; LocalBluetoothManager mManager; private CachedBluetoothDevice mCachedDevice; - private List mAllOfCachedDevices; + private Set mCachedDeviceGroup; @VisibleForTesting Handler mHandler = new Handler(Looper.getMainLooper()); @VisibleForTesting @@ -128,7 +128,7 @@ public class LeAudioBluetoothDetailsHeaderController extends BasePreferenceContr return; } mIsRegisterCallback = true; - for (CachedBluetoothDevice item : mAllOfCachedDevices) { + for (CachedBluetoothDevice item : mCachedDeviceGroup) { item.registerCallback(this); } refresh(); @@ -139,7 +139,7 @@ public class LeAudioBluetoothDetailsHeaderController extends BasePreferenceContr if (!mIsRegisterCallback) { return; } - for (CachedBluetoothDevice item : mAllOfCachedDevices) { + for (CachedBluetoothDevice item : mCachedDeviceGroup) { item.unregisterCallback(this); } @@ -155,7 +155,7 @@ public class LeAudioBluetoothDetailsHeaderController extends BasePreferenceContr mCachedDevice = cachedBluetoothDevice; mManager = bluetoothManager; mProfileManager = bluetoothManager.getProfileManager(); - mAllOfCachedDevices = Utils.getAllOfCachedBluetoothDevices(mManager, mCachedDevice); + mCachedDeviceGroup = Utils.findAllCachedBluetoothDevicesByGroupId(mManager, mCachedDevice); } @VisibleForTesting @@ -230,7 +230,7 @@ public class LeAudioBluetoothDetailsHeaderController extends BasePreferenceContr // Init the battery layouts. hideAllOfBatteryLayouts(); LeAudioProfile leAudioProfile = mProfileManager.getLeAudioProfile(); - if (mAllOfCachedDevices.isEmpty()) { + if (mCachedDeviceGroup.isEmpty()) { Log.e(TAG, "There is no LeAudioProfile."); return; } @@ -244,7 +244,7 @@ public class LeAudioBluetoothDetailsHeaderController extends BasePreferenceContr return; } - for (CachedBluetoothDevice cachedDevice : mAllOfCachedDevices) { + for (CachedBluetoothDevice cachedDevice : mCachedDeviceGroup) { int deviceId = leAudioProfile.getAudioLocation(cachedDevice.getDevice()); Log.d(TAG, "LeAudioDevices:" + cachedDevice.getDevice().getAnonymizedAddress() + ", deviceId:" + deviceId); @@ -300,15 +300,15 @@ public class LeAudioBluetoothDetailsHeaderController extends BasePreferenceContr @Override public void onDeviceAttributesChanged() { - for (CachedBluetoothDevice item : mAllOfCachedDevices) { + for (CachedBluetoothDevice item : mCachedDeviceGroup) { item.unregisterCallback(this); } - mAllOfCachedDevices = Utils.getAllOfCachedBluetoothDevices(mManager, mCachedDevice); - for (CachedBluetoothDevice item : mAllOfCachedDevices) { + mCachedDeviceGroup = Utils.findAllCachedBluetoothDevicesByGroupId(mManager, mCachedDevice); + for (CachedBluetoothDevice item : mCachedDeviceGroup) { item.registerCallback(this); } - if (!mAllOfCachedDevices.isEmpty()) { + if (!mCachedDeviceGroup.isEmpty()) { refresh(); } } diff --git a/src/com/android/settings/bluetooth/Utils.java b/src/com/android/settings/bluetooth/Utils.java index f6288b2c85c..b1d9de7bd3d 100644 --- a/src/com/android/settings/bluetooth/Utils.java +++ b/src/com/android/settings/bluetooth/Utils.java @@ -48,8 +48,9 @@ import com.android.settingslib.utils.ThreadUtils; import com.google.common.base.Supplier; -import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; @@ -239,12 +240,12 @@ public final class Utils { * @param cachedBluetoothDevice The main cachedBluetoothDevice. * @return all cachedBluetoothDevices with the same groupId. */ - public static List getAllOfCachedBluetoothDevices( + public static Set findAllCachedBluetoothDevicesByGroupId( LocalBluetoothManager localBtMgr, CachedBluetoothDevice cachedBluetoothDevice) { - List cachedBluetoothDevices = new ArrayList<>(); + Set cachedBluetoothDevices = new HashSet<>(); if (cachedBluetoothDevice == null) { - Log.e(TAG, "getAllOfCachedBluetoothDevices: no cachedBluetoothDevice"); + Log.e(TAG, "findAllCachedBluetoothDevicesByGroupId: no cachedBluetoothDevice"); return cachedBluetoothDevices; } int deviceGroupId = cachedBluetoothDevice.getGroupId(); @@ -254,7 +255,7 @@ public final class Utils { } if (localBtMgr == null) { - Log.e(TAG, "getAllOfCachedBluetoothDevices: no LocalBluetoothManager"); + Log.e(TAG, "findAllCachedBluetoothDevicesByGroupId: no LocalBluetoothManager"); return cachedBluetoothDevices; } CachedBluetoothDevice mainDevice = @@ -262,16 +263,14 @@ public final class Utils { .filter(cachedDevice -> cachedDevice.getGroupId() == deviceGroupId) .findFirst().orElse(null); if (mainDevice == null) { - Log.e(TAG, "getAllOfCachedBluetoothDevices: groupId = " + deviceGroupId + Log.e(TAG, "findAllCachedBluetoothDevicesByGroupId: groupId = " + deviceGroupId + ", no main device."); return cachedBluetoothDevices; } cachedBluetoothDevice = mainDevice; cachedBluetoothDevices.add(cachedBluetoothDevice); - for (CachedBluetoothDevice member : cachedBluetoothDevice.getMemberDevice()) { - cachedBluetoothDevices.add(member); - } - Log.d(TAG, "getAllOfCachedBluetoothDevices: groupId = " + deviceGroupId + cachedBluetoothDevices.addAll(cachedBluetoothDevice.getMemberDevice()); + Log.d(TAG, "findAllCachedBluetoothDevicesByGroupId: groupId = " + deviceGroupId + " , cachedBluetoothDevice = " + cachedBluetoothDevice + " , deviceList = " + cachedBluetoothDevices); return cachedBluetoothDevices; diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePreferenceTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePreferenceTest.java index 03113421d3f..ba90ccf63d0 100644 --- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePreferenceTest.java +++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDevicePreferenceTest.java @@ -18,10 +18,10 @@ package com.android.settings.bluetooth; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; -import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -32,22 +32,31 @@ import android.content.Context; import android.graphics.drawable.Drawable; import android.os.UserManager; import android.util.Pair; -import android.view.ContextThemeWrapper; + +import androidx.test.core.app.ApplicationProvider; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.settings.R; import com.android.settings.testutils.FakeFeatureFactory; import com.android.settings.testutils.shadow.ShadowAlertDialogCompat; +import com.android.settings.testutils.shadow.ShadowBluetoothUtils; import com.android.settingslib.bluetooth.CachedBluetoothDevice; +import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; +import com.android.settingslib.bluetooth.LocalBluetoothManager; import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; + import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; -import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import org.robolectric.util.ReflectionHelpers; @@ -57,18 +66,21 @@ import java.util.Comparator; import java.util.List; @RunWith(RobolectricTestRunner.class) -@Config(shadows = {ShadowAlertDialogCompat.class}) +@Config(shadows = {ShadowAlertDialogCompat.class, + com.android.settings.testutils.shadow.ShadowBluetoothUtils.class}) public class BluetoothDevicePreferenceTest { private static final boolean SHOW_DEVICES_WITHOUT_NAMES = true; - private static final String MAC_ADDRESS = "04:52:C7:0B:D8:3C"; - private static final String MAC_ADDRESS_2 = "05:52:C7:0B:D8:3C"; - private static final String MAC_ADDRESS_3 = "06:52:C7:0B:D8:3C"; - private static final String MAC_ADDRESS_4 = "07:52:C7:0B:D8:3C"; + private static final String TEST_MAC_ADDRESS = "04:52:C7:0B:D8:3C"; + private static final String TEST_MAC_ADDRESS_1 = "05:52:C7:0B:D8:3C"; + private static final String TEST_MAC_ADDRESS_2 = "06:52:C7:0B:D8:3C"; + private static final String TEST_MAC_ADDRESS_3 = "07:52:C7:0B:D8:3C"; private static final Comparator COMPARATOR = Comparator.naturalOrder(); private static final String FAKE_DESCRIPTION = "fake_description"; + private static final int TEST_DEVICE_GROUP_ID = 1; - private Context mContext; + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); @Mock private CachedBluetoothDevice mCachedBluetoothDevice; @Mock @@ -89,35 +101,37 @@ public class BluetoothDevicePreferenceTest { private Drawable mDrawable; @Mock private BluetoothAdapter mBluetoothAdapter; + @Mock + private LocalBluetoothManager mLocalBluetoothManager; + @Mock + private CachedBluetoothDeviceManager mDeviceManager; + private Context mContext = ApplicationProvider.getApplicationContext(); private FakeFeatureFactory mFakeFeatureFactory; private MetricsFeatureProvider mMetricsFeatureProvider; + private BluetoothDevicePreference mPreference; private List mPreferenceList = new ArrayList<>(); @Before public void setUp() { - MockitoAnnotations.initMocks(this); - Context context = spy(RuntimeEnvironment.application.getApplicationContext()); - mContext = new ContextThemeWrapper(context, R.style.Theme_Settings); + mContext.setTheme(R.style.Theme_Settings); mFakeFeatureFactory = FakeFeatureFactory.setupForTest(); mMetricsFeatureProvider = mFakeFeatureFactory.getMetricsFeatureProvider(); - when(mCachedBluetoothDevice.getAddress()).thenReturn(MAC_ADDRESS); - when(mCachedBluetoothDevice.getDrawableWithDescription()) - .thenReturn(new Pair<>(mDrawable, FAKE_DESCRIPTION)); - when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice); - when(mCachedDevice1.getAddress()).thenReturn(MAC_ADDRESS_2); - when(mCachedDevice1.getDrawableWithDescription()) - .thenReturn(new Pair<>(mDrawable, FAKE_DESCRIPTION)); - when(mCachedDevice1.getDevice()).thenReturn(mBluetoothDevice1); - when(mCachedDevice2.getAddress()).thenReturn(MAC_ADDRESS_3); - when(mCachedDevice2.getDrawableWithDescription()) - .thenReturn(new Pair<>(mDrawable, FAKE_DESCRIPTION)); - when(mCachedDevice2.getDevice()).thenReturn(mBluetoothDevice2); - when(mCachedDevice3.getAddress()).thenReturn(MAC_ADDRESS_4); - when(mCachedDevice3.getDrawableWithDescription()) - .thenReturn(new Pair<>(mDrawable, FAKE_DESCRIPTION)); - when(mCachedDevice3.getDevice()).thenReturn(mBluetoothDevice3); + ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager; + mLocalBluetoothManager = Utils.getLocalBtManager(mContext); + when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(mDeviceManager); + prepareCachedBluetoothDevice(mCachedBluetoothDevice, TEST_MAC_ADDRESS, + new Pair<>(mDrawable, FAKE_DESCRIPTION), TEST_DEVICE_GROUP_ID, mBluetoothDevice); + prepareCachedBluetoothDevice(mCachedDevice1, TEST_MAC_ADDRESS_1, + new Pair<>(mDrawable, FAKE_DESCRIPTION), TEST_DEVICE_GROUP_ID, mBluetoothDevice1); + prepareCachedBluetoothDevice(mCachedDevice2, TEST_MAC_ADDRESS_2, + new Pair<>(mDrawable, FAKE_DESCRIPTION), TEST_DEVICE_GROUP_ID, mBluetoothDevice2); + prepareCachedBluetoothDevice(mCachedDevice3, TEST_MAC_ADDRESS_3, + new Pair<>(mDrawable, FAKE_DESCRIPTION), TEST_DEVICE_GROUP_ID, mBluetoothDevice3); + when(mDeviceManager.getCachedDevicesCopy()).thenReturn( + ImmutableList.of(mCachedBluetoothDevice)); + mPreference = new BluetoothDevicePreference(mContext, mCachedBluetoothDevice, SHOW_DEVICES_WITHOUT_NAMES, BluetoothDevicePreference.SortType.TYPE_DEFAULT); mPreference.mBluetoothAdapter = mBluetoothAdapter; @@ -301,7 +315,8 @@ public class BluetoothDevicePreferenceTest { // callback is not removed. mPreference.onAttached(); - verify(mCachedBluetoothDevice, times(1)).registerCallback(any()); + verify(mCachedBluetoothDevice, times(1)).registerCallback(eq(mContext.getMainExecutor()), + any()); verify(mBluetoothAdapter, times(1)).addOnMetadataChangedListener(any(), any(), any()); } @@ -313,7 +328,99 @@ public class BluetoothDevicePreferenceTest { mPreference.onAttached(); verify(mCachedBluetoothDevice, times(1)).unregisterCallback(any()); - verify(mCachedBluetoothDevice, times(2)).registerCallback(any()); + verify(mCachedBluetoothDevice, times(2)).registerCallback(eq(mContext.getMainExecutor()), + any()); verify(mBluetoothAdapter, times(2)).addOnMetadataChangedListener(any(), any(), any()); } + + @Test + public void onDeviceAttributesChanged_updatePreference() { + when(mCachedBluetoothDevice.getName()).thenReturn("Name"); + mPreference.onAttached(); + final String updatedName = "updatedName"; + when(mCachedBluetoothDevice.getName()).thenReturn(updatedName); + + getCachedBluetoothDeviceCallback().onDeviceAttributesChanged(); + + assertThat(mPreference.getTitle().toString()).isEqualTo(updatedName); + } + + @Test + public void onAttached_memberDevicesAdded_registerAllCallback() { + when(mCachedBluetoothDevice.getMemberDevice()).thenReturn( + ImmutableSet.of(mCachedDevice1, mCachedDevice2, mCachedDevice3)); + when(mDeviceManager.getCachedDevicesCopy()).thenReturn( + ImmutableList.of(mCachedBluetoothDevice, mCachedDevice1, mCachedDevice2, + mCachedDevice3)); + mPreference = new BluetoothDevicePreference(mContext, mCachedBluetoothDevice, + SHOW_DEVICES_WITHOUT_NAMES, BluetoothDevicePreference.SortType.TYPE_DEFAULT); + + mPreference.onAttached(); + + verify(mCachedBluetoothDevice).registerCallback(eq(mContext.getMainExecutor()), any()); + verify(mCachedDevice1).registerCallback(eq(mContext.getMainExecutor()), any()); + verify(mCachedDevice2).registerCallback(eq(mContext.getMainExecutor()), any()); + verify(mCachedDevice3).registerCallback(eq(mContext.getMainExecutor()), any()); + } + + @Test + public void onDetached_memberDevicesAdded_unregisterAllCallback() { + when(mCachedBluetoothDevice.getMemberDevice()).thenReturn( + ImmutableSet.of(mCachedDevice1, mCachedDevice2, mCachedDevice3)); + when(mDeviceManager.getCachedDevicesCopy()).thenReturn( + ImmutableList.of(mCachedBluetoothDevice, mCachedDevice1, mCachedDevice2, + mCachedDevice3)); + mPreference = new BluetoothDevicePreference(mContext, mCachedBluetoothDevice, + SHOW_DEVICES_WITHOUT_NAMES, BluetoothDevicePreference.SortType.TYPE_DEFAULT); + + mPreference.onAttached(); + mPreference.onDetached(); + + verify(mCachedBluetoothDevice).unregisterCallback(any()); + verify(mCachedDevice1).unregisterCallback(any()); + verify(mCachedDevice2).unregisterCallback(any()); + verify(mCachedDevice3).unregisterCallback(any()); + } + + @Test + public void onDeviceAttributesChanged_memberDevicesChanged_registerOnlyExistDeviceCallback() { + when(mCachedBluetoothDevice.getMemberDevice()).thenReturn( + ImmutableSet.of(mCachedDevice1, mCachedDevice2, mCachedDevice3)); + when(mDeviceManager.getCachedDevicesCopy()).thenReturn( + ImmutableList.of(mCachedBluetoothDevice, mCachedDevice1, mCachedDevice2, + mCachedDevice3)); + mPreference = new BluetoothDevicePreference(mContext, mCachedBluetoothDevice, + SHOW_DEVICES_WITHOUT_NAMES, BluetoothDevicePreference.SortType.TYPE_DEFAULT); + mPreference.onAttached(); + when(mCachedBluetoothDevice.getMemberDevice()).thenReturn( + ImmutableSet.of(mCachedDevice1, mCachedDevice2)); + when(mDeviceManager.getCachedDevicesCopy()).thenReturn( + ImmutableList.of(mCachedBluetoothDevice, mCachedDevice1, mCachedDevice2)); + + getCachedBluetoothDeviceCallback().onDeviceAttributesChanged(); + + verify(mCachedBluetoothDevice, times(2)).registerCallback(eq(mContext.getMainExecutor()), + any()); + verify(mCachedDevice1, times(2)).registerCallback(eq(mContext.getMainExecutor()), any()); + verify(mCachedDevice2, times(2)).registerCallback(eq(mContext.getMainExecutor()), any()); + verify(mCachedDevice3, times(1)).registerCallback(eq(mContext.getMainExecutor()), any()); + } + + private void prepareCachedBluetoothDevice(CachedBluetoothDevice cachedDevice, String address, + Pair drawableWithDescription, int groupId, + BluetoothDevice bluetoothDevice) { + when(cachedDevice.getAddress()).thenReturn(address); + when(cachedDevice.getDrawableWithDescription()).thenReturn(drawableWithDescription); + when(cachedDevice.getGroupId()).thenReturn(groupId); + when(cachedDevice.getDevice()).thenReturn(bluetoothDevice); + } + + private CachedBluetoothDevice.Callback getCachedBluetoothDeviceCallback() { + ArgumentCaptor callbackCaptor = ArgumentCaptor.forClass( + CachedBluetoothDevice.Callback.class); + verify(mCachedBluetoothDevice).registerCallback(eq(mContext.getMainExecutor()), + callbackCaptor.capture()); + + return callbackCaptor.getValue(); + } }