Merge "[Settings] Avoid NPE if BT device is changed by framework." into udc-dev
diff --git a/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java b/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java
index 666a738..4679eb2 100644
--- a/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java
+++ b/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java
@@ -57,7 +57,9 @@
 
 import java.io.IOException;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -92,6 +94,7 @@
     @VisibleForTesting
     final Map<String, Bitmap> mIconCache;
     private CachedBluetoothDevice mCachedDevice;
+    private Set<BluetoothDevice> mBluetoothDevices;
     @VisibleForTesting
     BluetoothAdapter mBluetoothAdapter;
     @VisibleForTesting
@@ -141,23 +144,13 @@
         if (!isAvailable()) {
             return;
         }
-        mIsRegisterCallback = true;
-        mCachedDevice.registerCallback(this);
-        mBluetoothAdapter.addOnMetadataChangedListener(mCachedDevice.getDevice(),
-                mContext.getMainExecutor(), mMetadataListener);
-
+        registerBluetoothDevice();
         refresh();
     }
 
     @Override
     public void onStop() {
-        if (!mIsRegisterCallback) {
-            return;
-        }
-        mCachedDevice.unregisterCallback(this);
-        mBluetoothAdapter.removeOnMetadataChangedListener(mCachedDevice.getDevice(),
-                mMetadataListener);
-        mIsRegisterCallback = false;
+        unRegisterBluetoothDevice();
     }
 
     @Override
@@ -175,6 +168,40 @@
         mCachedDevice = cachedBluetoothDevice;
     }
 
+    private void registerBluetoothDevice() {
+        if (mBluetoothDevices == null) {
+            mBluetoothDevices = new HashSet<>();
+        }
+        mBluetoothDevices.clear();
+        if (mCachedDevice.getDevice() != null) {
+            mBluetoothDevices.add(mCachedDevice.getDevice());
+        }
+        mCachedDevice.getMemberDevice().forEach(cbd -> {
+            if (cbd != null) {
+                mBluetoothDevices.add(cbd.getDevice());
+            }
+        });
+        if (mBluetoothDevices.isEmpty()) {
+            Log.d(TAG, "No BT devcie to register.");
+            return;
+        }
+        mCachedDevice.registerCallback(this);
+        mBluetoothDevices.forEach(bd ->
+                mBluetoothAdapter.addOnMetadataChangedListener(bd,
+                        mContext.getMainExecutor(), mMetadataListener));
+    }
+
+    private void unRegisterBluetoothDevice() {
+        if (mBluetoothDevices == null || mBluetoothDevices.isEmpty()) {
+            Log.d(TAG, "No BT devcie to unregister.");
+            return;
+        }
+        mCachedDevice.unregisterCallback(this);
+        mBluetoothDevices.forEach(bd -> mBluetoothAdapter.removeOnMetadataChangedListener(bd,
+                mMetadataListener));
+        mBluetoothDevices.clear();
+    }
+
     @VisibleForTesting
     void refresh() {
         if (mLayoutPreference != null && mCachedDevice != null) {
diff --git a/tests/robotests/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderControllerTest.java
index e334af5..2c9fb99 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderControllerTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderControllerTest.java
@@ -56,6 +56,9 @@
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 
+import java.util.HashSet;
+import java.util.Set;
+
 @RunWith(RobolectricTestRunner.class)
 @Config(shadows = {ShadowEntityHeaderController.class, ShadowDeviceConfig.class})
 public class AdvancedBluetoothDetailsHeaderControllerTest {
@@ -380,40 +383,68 @@
                 SettingsUIDeviceConfig.BT_ADVANCED_HEADER_ENABLED, "true", true);
         when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
                 .thenReturn("true".getBytes());
+        Set<CachedBluetoothDevice> cacheBluetoothDevices = new HashSet<>();
+        when(mCachedDevice.getMemberDevice()).thenReturn(cacheBluetoothDevices);
 
         mController.onStart();
 
+        verify(mCachedDevice).registerCallback(mController);
         verify(mBluetoothAdapter).addOnMetadataChangedListener(mBluetoothDevice,
                 mContext.getMainExecutor(), mController.mMetadataListener);
     }
 
     @Test
-    public void onStop_isRegisterCallback_unregisterCallback() {
-        mController.mIsRegisterCallback = true;
-
-        mController.onStop();
-
-        verify(mBluetoothAdapter).removeOnMetadataChangedListener(mBluetoothDevice,
-                mController.mMetadataListener);
-    }
-
-    @Test
-    public void onStart_notAvailable_registerCallback() {
+    public void onStart_notAvailable_notNeedToRegisterCallback() {
         when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
                 .thenReturn("false".getBytes());
 
         mController.onStart();
 
+        verify(mCachedDevice, never()).registerCallback(mController);
         verify(mBluetoothAdapter, never()).addOnMetadataChangedListener(mBluetoothDevice,
                 mContext.getMainExecutor(), mController.mMetadataListener);
     }
 
     @Test
-    public void onStop_notRegisterCallback_unregisterCallback() {
-        mController.mIsRegisterCallback = false;
+    public void onStart_isAvailableButNoBluetoothDevice_notNeedToRegisterCallback() {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_UI,
+                SettingsUIDeviceConfig.BT_ADVANCED_HEADER_ENABLED, "true", true);
+        when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
+                .thenReturn("true".getBytes());
+        when(mCachedDevice.getDevice()).thenReturn(null);
+        Set<CachedBluetoothDevice> cacheBluetoothDevices = new HashSet<>();
+        when(mCachedDevice.getMemberDevice()).thenReturn(cacheBluetoothDevices);
+
+        mController.onStart();
+
+        verify(mCachedDevice, never()).registerCallback(mController);
+        verify(mBluetoothAdapter, never()).addOnMetadataChangedListener(mBluetoothDevice,
+                mContext.getMainExecutor(), mController.mMetadataListener);
+    }
+
+    @Test
+    public void onStop_availableAndHasBluetoothDevice_unregisterCallback() {
+        onStart_isAvailable_registerCallback();
 
         mController.onStop();
 
+        verify(mCachedDevice).unregisterCallback(mController);
+        verify(mBluetoothAdapter).removeOnMetadataChangedListener(mBluetoothDevice,
+                mController.mMetadataListener);
+    }
+
+    @Test
+    public void onStop_noBluetoothDevice_noNeedToUnregisterCallback() {
+        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_SETTINGS_UI,
+                SettingsUIDeviceConfig.BT_ADVANCED_HEADER_ENABLED, "true", true);
+        when(mBluetoothDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET))
+                .thenReturn("true".getBytes());
+        when(mCachedDevice.getDevice()).thenReturn(null);
+
+        mController.onStart();
+        mController.onStop();
+
+        verify(mCachedDevice, never()).unregisterCallback(mController);
         verify(mBluetoothAdapter, never()).removeOnMetadataChangedListener(mBluetoothDevice,
                 mController.mMetadataListener);
     }