Fix issues in BT detail header

1. Update isAvailable() in controller
2. Update method to get fast pair icon

Bug: 124455912
Test: RunSettingsRoboTests
Change-Id: I24a04c8c91d74e9b8b7e8746ad6279fafa37f0a9
diff --git a/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java b/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java
index a09018e..34a758b 100644
--- a/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java
+++ b/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java
@@ -18,16 +18,19 @@
 
 import android.bluetooth.BluetoothDevice;
 import android.content.Context;
+import android.graphics.Bitmap;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffColorFilter;
 import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.util.Log;
 import android.view.View;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import androidx.annotation.VisibleForTesting;
-import androidx.core.graphics.drawable.IconCompat;
 import androidx.preference.PreferenceScreen;
 
 import com.android.settings.R;
@@ -37,20 +40,29 @@
 import com.android.settingslib.core.lifecycle.LifecycleObserver;
 import com.android.settingslib.core.lifecycle.events.OnStart;
 import com.android.settingslib.core.lifecycle.events.OnStop;
+import com.android.settingslib.utils.ThreadUtils;
 import com.android.settingslib.widget.LayoutPreference;
 
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
 /**
  * This class adds a header with device name and status (connected/disconnected, etc.).
  */
 public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceController implements
         LifecycleObserver, OnStart, OnStop, CachedBluetoothDevice.Callback {
+    private static final String TAG = "AdvancedBtHeaderCtrl";
 
     @VisibleForTesting
     LayoutPreference mLayoutPreference;
+    @VisibleForTesting
+    final Map<String, Bitmap> mIconCache;
     private CachedBluetoothDevice mCachedDevice;
 
     public AdvancedBluetoothDetailsHeaderController(Context context, String prefKey) {
         super(context, prefKey);
+        mIconCache = new HashMap<>();
     }
 
     @Override
@@ -65,6 +77,7 @@
         super.displayPreference(screen);
         mLayoutPreference = screen.findPreference(getPreferenceKey());
         mLayoutPreference.setVisible(isAvailable());
+
         refresh();
     }
 
@@ -76,6 +89,14 @@
     @Override
     public void onStop() {
         mCachedDevice.unregisterCallback(this::onDeviceAttributesChanged);
+
+        // Destroy icon bitmap associated with this header
+        for (Bitmap bitmap : mIconCache.values()) {
+            if (bitmap != null) {
+                bitmap.recycle();
+            }
+        }
+        mIconCache.clear();
     }
 
     public void init(CachedBluetoothDevice cachedBluetoothDevice) {
@@ -140,8 +161,7 @@
         final String iconUri = Utils.getStringMetaData(bluetoothDevice, iconMetaKey);
         if (iconUri != null) {
             final ImageView imageView = linearLayout.findViewById(R.id.header_icon);
-            final IconCompat iconCompat = IconCompat.createWithContentUri(iconUri);
-            imageView.setImageBitmap(iconCompat.getBitmap());
+            updateIcon(imageView, iconUri);
         }
 
         final int batteryLevel = Utils.getIntMetaData(bluetoothDevice, batteryMetaKey);
@@ -181,11 +201,35 @@
                 BluetoothDevice.METADATA_MAIN_ICON);
         if (iconUri != null) {
             final ImageView imageView = linearLayout.findViewById(R.id.header_icon);
-            final IconCompat iconCompat = IconCompat.createWithContentUri(iconUri);
-            imageView.setImageBitmap(iconCompat.getBitmap());
+            updateIcon(imageView, iconUri);
         }
     }
 
+    /**
+     * Update icon by {@code iconUri}. If icon exists in cache, use it; otherwise extract it
+     * from uri in background thread and update it in main thread.
+     */
+    @VisibleForTesting
+    void updateIcon(ImageView imageView, String iconUri) {
+        if (mIconCache.containsKey(iconUri)) {
+            imageView.setImageBitmap(mIconCache.get(iconUri));
+            return;
+        }
+
+        ThreadUtils.postOnBackgroundThread(() -> {
+            try {
+                final Bitmap bitmap = MediaStore.Images.Media.getBitmap(
+                        mContext.getContentResolver(), Uri.parse(iconUri));
+                ThreadUtils.postOnMainThread(() -> {
+                    mIconCache.put(iconUri, bitmap);
+                    imageView.setImageBitmap(bitmap);
+                });
+            } catch (IOException e) {
+                Log.e(TAG, "Failed to get bitmap for: " + iconUri);
+            }
+        });
+    }
+
     @Override
     public void onDeviceAttributesChanged() {
         if (mCachedDevice != null) {
diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsButtonsController.java b/src/com/android/settings/bluetooth/BluetoothDetailsButtonsController.java
index af15052..a7fae14 100644
--- a/src/com/android/settings/bluetooth/BluetoothDetailsButtonsController.java
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsButtonsController.java
@@ -16,7 +16,6 @@
 
 package com.android.settings.bluetooth;
 
-import android.bluetooth.BluetoothDevice;
 import android.content.Context;
 
 import androidx.preference.PreferenceFragmentCompat;
@@ -44,13 +43,6 @@
         mIsConnected = device.isConnected();
     }
 
-    @Override
-    public boolean isAvailable() {
-        final boolean unthetheredHeadset = Utils.getBooleanMetaData(mCachedDevice.getDevice(),
-                BluetoothDevice.METADATA_IS_UNTHETHERED_HEADSET);
-        return !unthetheredHeadset;
-    }
-
     private void onForgetButtonPressed() {
         ForgetDeviceDialogFragment fragment =
                 ForgetDeviceDialogFragment.newInstance(mCachedDevice.getAddress());
diff --git a/src/com/android/settings/bluetooth/BluetoothDetailsHeaderController.java b/src/com/android/settings/bluetooth/BluetoothDetailsHeaderController.java
index 994daa7..fd805b8 100644
--- a/src/com/android/settings/bluetooth/BluetoothDetailsHeaderController.java
+++ b/src/com/android/settings/bluetooth/BluetoothDetailsHeaderController.java
@@ -16,6 +16,7 @@
 
 package com.android.settings.bluetooth;
 
+import android.bluetooth.BluetoothDevice;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.util.Pair;
@@ -51,6 +52,12 @@
     }
 
     @Override
+    public boolean isAvailable() {
+        return !Utils.getBooleanMetaData(mCachedDevice.getDevice(),
+                BluetoothDevice.METADATA_IS_UNTHETHERED_HEADSET);
+    }
+
+    @Override
     protected void init(PreferenceScreen screen) {
         final LayoutPreference headerPreference = screen.findPreference(KEY_DEVICE_HEADER);
         mHeaderController = EntityHeaderController.newInstance(mFragment.getActivity(), mFragment,
diff --git a/tests/robotests/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderControllerTest.java
index e646273..53fac3c 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderControllerTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderControllerTest.java
@@ -18,13 +18,16 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.bluetooth.BluetoothDevice;
 import android.content.Context;
+import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
@@ -50,12 +53,17 @@
     private static final int BATTERY_LEVEL_MAIN = 30;
     private static final int BATTERY_LEVEL_LEFT = 25;
     private static final int BATTERY_LEVEL_RIGHT = 45;
+    private static final String ICON_URI = "content://test.provider/icon.png";
 
     private Context mContext;
 
     @Mock
     private BluetoothDevice mBluetoothDevice;
     @Mock
+    private Bitmap mBitmap;
+    @Mock
+    private ImageView mImageView;
+    @Mock
     private CachedBluetoothDevice mCachedDevice;
     private AdvancedBluetoothDetailsHeaderController mController;
     private LayoutPreference mLayoutPreference;
@@ -142,6 +150,15 @@
                 BasePreferenceController.CONDITIONALLY_UNAVAILABLE);
     }
 
+    @Test
+    public void updateIcon_existInCache_setImageBitmap() {
+        mController.mIconCache.put(ICON_URI, mBitmap);
+
+        mController.updateIcon(mImageView, ICON_URI);
+
+        verify(mImageView).setImageBitmap(mBitmap);
+    }
+
     private void assertBatteryLevel(LinearLayout linearLayout, int batteryLevel) {
         final TextView textView = linearLayout.findViewById(R.id.bt_battery_summary);
         assertThat(textView.getText().toString()).isEqualTo(
diff --git a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHeaderControllerTest.java b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHeaderControllerTest.java
index 930d9cb..cf119ea 100644
--- a/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHeaderControllerTest.java
+++ b/tests/robotests/src/com/android/settings/bluetooth/BluetoothDetailsHeaderControllerTest.java
@@ -19,18 +19,12 @@
 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.inOrder;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.bluetooth.BluetoothDevice;
 import android.graphics.drawable.Drawable;
-import android.view.View;
-
-import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentManager;
-import androidx.fragment.app.FragmentTransaction;
 
 import com.android.settings.R;
 import com.android.settings.testutils.FakeFeatureFactory;
@@ -62,6 +56,8 @@
     private LocalBluetoothManager mBluetoothManager;
     @Mock
     private CachedBluetoothDeviceManager mCachedDeviceManager;
+    @Mock
+    private BluetoothDevice mBluetoothDevice;
 
     @Override
     public void setUp() {
@@ -77,6 +73,7 @@
         mPreference.setKey(mController.getPreferenceKey());
         mScreen.addPreference(mPreference);
         setupDevice(mDeviceConfig);
+        when(mCachedDevice.getDevice()).thenReturn(mBluetoothDevice);
     }
 
     @After
@@ -124,4 +121,12 @@
         inOrder.verify(mHeaderController)
             .setSummary(mContext.getString(R.string.bluetooth_connecting));
     }
+
+    @Test
+    public void isAvailable_unthetheredHeadset_returnFalse() {
+        when(mBluetoothDevice.getMetadata(
+                BluetoothDevice.METADATA_IS_UNTHETHERED_HEADSET)).thenReturn("true");
+
+        assertThat(mController.isAvailable()).isFalse();
+    }
 }