Fix bluetooth settings force close

This CL include following change:

- Add null check of cacedBluetoothDevice, cachedBluetooth could be null
  if the input BluetoothDevice can't found in
  CachedBluetoothDeviceManager.
- Move addPreference() and removePreference() to update() method,
  because update() will be called to update UI when recevice Bluetooth
  device change event.
- Add test case to test CachedBluetoothDevice null check.

Bug: 149068434
Test: make -j42 RunSettingsRoboTests
Change-Id: I90016bf1175925821b0d9b634c62cf796289a734
diff --git a/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdater.java b/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdater.java
index 6893c25..9a327ed 100644
--- a/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdater.java
+++ b/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdater.java
@@ -20,6 +20,7 @@
 import android.content.Context;
 import android.util.Log;
 
+import androidx.annotation.VisibleForTesting;
 import androidx.preference.Preference;
 
 import com.android.settings.connecteddevice.DevicePreferenceCallback;
@@ -38,24 +39,25 @@
 
     private static final String PREF_KEY = "saved_bt";
 
+    @VisibleForTesting
+    BluetoothAdapter mBluetoothAdapter;
+
     public SavedBluetoothDeviceUpdater(Context context, DashboardFragment fragment,
             DevicePreferenceCallback devicePreferenceCallback) {
         super(context, fragment, devicePreferenceCallback);
+
+        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
     }
 
     @Override
     public void forceUpdate() {
-        if (BluetoothAdapter.getDefaultAdapter().isEnabled()) {
+        if (mBluetoothAdapter.isEnabled()) {
             final CachedBluetoothDeviceManager cachedManager =
                     mLocalManager.getCachedDeviceManager();
-            for (BluetoothDevice device
-                    : BluetoothAdapter.getDefaultAdapter().getMostRecentlyConnectedDevices()) {
+            for (BluetoothDevice device : mBluetoothAdapter.getMostRecentlyConnectedDevices()) {
                 final CachedBluetoothDevice cachedDevice = cachedManager.findDevice(device);
-                if (isFilterMatched(cachedDevice)) {
-                    // Add the preference if it is new one
-                    addPreference(cachedDevice, BluetoothDevicePreference.SortType.TYPE_NO_SORT);
-                } else {
-                    removePreference(cachedDevice);
+                if (cachedDevice != null) {
+                    update(cachedDevice);
                 }
             }
         } else {
@@ -64,6 +66,16 @@
     }
 
     @Override
+    public void update(CachedBluetoothDevice cachedDevice) {
+        if (isFilterMatched(cachedDevice)) {
+            // Add the preference if it is new one
+            addPreference(cachedDevice, BluetoothDevicePreference.SortType.TYPE_NO_SORT);
+        } else {
+            removePreference(cachedDevice);
+        }
+    }
+
+    @Override
     public boolean isFilterMatched(CachedBluetoothDevice cachedDevice) {
         final BluetoothDevice device = cachedDevice.getDevice();
         if (DBG) {
diff --git a/tests/robotests/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdaterTest.java b/tests/robotests/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdaterTest.java
index d329f5c..3993475 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdaterTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/SavedBluetoothDeviceUpdaterTest.java
@@ -18,10 +18,12 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
@@ -30,6 +32,8 @@
 import com.android.settings.dashboard.DashboardFragment;
 import com.android.settings.testutils.shadow.ShadowBluetoothAdapter;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -40,6 +44,10 @@
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
 @RunWith(RobolectricTestRunner.class)
 @Config(shadows = {ShadowBluetoothAdapter.class})
 public class SavedBluetoothDeviceUpdaterTest {
@@ -54,6 +62,12 @@
     private CachedBluetoothDevice mCachedBluetoothDevice;
     @Mock
     private BluetoothDevice mBluetoothDevice;
+    @Mock
+    private BluetoothAdapter mBluetoothAdapter;
+    @Mock
+    private CachedBluetoothDeviceManager mDeviceManager;
+    @Mock
+    private LocalBluetoothManager mBluetoothManager;
 
     private Context mContext;
     private SavedBluetoothDeviceUpdater mBluetoothDeviceUpdater;
@@ -72,6 +86,8 @@
         mBluetoothDeviceUpdater = spy(new SavedBluetoothDeviceUpdater(mContext, mDashboardFragment,
                 mDevicePreferenceCallback));
         mBluetoothDeviceUpdater.setPrefContext(mContext);
+        mBluetoothDeviceUpdater.mBluetoothAdapter = mBluetoothAdapter;
+        mBluetoothDeviceUpdater.mLocalManager = mBluetoothManager;
         mPreference = new BluetoothDevicePreference(mContext, mCachedBluetoothDevice,
                 false, BluetoothDevicePreference.SortType.TYPE_DEFAULT);
         doNothing().when(mBluetoothDeviceUpdater).addPreference(any());
@@ -85,7 +101,8 @@
 
         mBluetoothDeviceUpdater.update(mCachedBluetoothDevice);
 
-        verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice);
+        verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice,
+                BluetoothDevicePreference.SortType.TYPE_NO_SORT);
     }
 
     @Test
@@ -115,7 +132,8 @@
         mBluetoothDeviceUpdater.onProfileConnectionStateChanged(mCachedBluetoothDevice,
                 BluetoothProfile.STATE_DISCONNECTED, BluetoothProfile.A2DP);
 
-        verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice);
+        verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice,
+                BluetoothDevicePreference.SortType.TYPE_NO_SORT);
     }
 
     @Test
@@ -124,4 +142,70 @@
 
         verify(mCachedBluetoothDevice).connect();
     }
+
+    @Test
+    public void forceUpdate_findCachedBluetoothDeviceIsMatched_addPreference() {
+        final List<BluetoothDevice> bluetoothDevices = new ArrayList<>();
+        bluetoothDevices.add(mBluetoothDevice);
+
+        when(mBluetoothAdapter.isEnabled()).thenReturn(true);
+        when(mBluetoothAdapter.getMostRecentlyConnectedDevices()).thenReturn(bluetoothDevices);
+        when(mBluetoothManager.getCachedDeviceManager()).thenReturn(mDeviceManager);
+        when(mDeviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedBluetoothDevice);
+        when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        when(mBluetoothDevice.isConnected()).thenReturn(false);
+
+        mBluetoothDeviceUpdater.forceUpdate();
+
+        verify(mBluetoothDeviceUpdater).addPreference(mCachedBluetoothDevice,
+                BluetoothDevicePreference.SortType.TYPE_NO_SORT);
+    }
+
+    @Test
+    public void forceUpdate_findCachedBluetoothDeviceNotMatched_removePreference() {
+        final List<BluetoothDevice> bluetoothDevices = new ArrayList<>();
+        bluetoothDevices.add(mBluetoothDevice);
+
+        when(mBluetoothAdapter.isEnabled()).thenReturn(true);
+        when(mBluetoothAdapter.getMostRecentlyConnectedDevices()).thenReturn(bluetoothDevices);
+        when(mBluetoothManager.getCachedDeviceManager()).thenReturn(mDeviceManager);
+        when(mDeviceManager.findDevice(mBluetoothDevice)).thenReturn(mCachedBluetoothDevice);
+        when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        when(mBluetoothDevice.isConnected()).thenReturn(true);
+
+        mBluetoothDeviceUpdater.forceUpdate();
+
+        verify(mBluetoothDeviceUpdater).removePreference(mCachedBluetoothDevice);
+    }
+
+    @Test
+    public void forceUpdate_notFindCachedBluetoothDevice_doNothing() {
+        final List<BluetoothDevice> bluetoothDevices = new ArrayList<>();
+        bluetoothDevices.add(mBluetoothDevice);
+
+        when(mBluetoothAdapter.isEnabled()).thenReturn(true);
+        when(mBluetoothAdapter.getMostRecentlyConnectedDevices()).thenReturn(bluetoothDevices);
+        when(mBluetoothManager.getCachedDeviceManager()).thenReturn(mDeviceManager);
+        when(mDeviceManager.findDevice(mBluetoothDevice)).thenReturn(null);
+
+        mBluetoothDeviceUpdater.forceUpdate();
+
+        verify(mBluetoothDeviceUpdater, never()).removePreference(mCachedBluetoothDevice);
+        verify(mBluetoothDeviceUpdater, never()).addPreference(mCachedBluetoothDevice,
+                BluetoothDevicePreference.SortType.TYPE_NO_SORT);
+    }
+
+    @Test
+    public void forceUpdate_bluetoothAdapterNotEnable_removeAllDevicesFromPreference() {
+        final Collection<CachedBluetoothDevice> cachedDevices = new ArrayList<>();
+        cachedDevices.add(mCachedBluetoothDevice);
+
+        when(mBluetoothManager.getCachedDeviceManager()).thenReturn(mDeviceManager);
+        when(mDeviceManager.getCachedDevicesCopy()).thenReturn(cachedDevices);
+        when(mBluetoothAdapter.isEnabled()).thenReturn(false);
+
+        mBluetoothDeviceUpdater.forceUpdate();
+
+        verify(mBluetoothDeviceUpdater).removeAllDevicesFromPreference();
+    }
 }