From 5e5dd25b8f4d170dda01a3f729068d277b202deb Mon Sep 17 00:00:00 2001 From: jackqdyulei Date: Fri, 22 Feb 2019 12:42:07 -0800 Subject: [PATCH] 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 --- .../AdvancedBluetoothDetailsHeaderController.java | 54 ++++++++++++++++++++-- .../BluetoothDetailsButtonsController.java | 8 ---- .../BluetoothDetailsHeaderController.java | 7 +++ ...vancedBluetoothDetailsHeaderControllerTest.java | 17 +++++++ .../BluetoothDetailsHeaderControllerTest.java | 19 +++++--- 5 files changed, 85 insertions(+), 20 deletions(-) diff --git a/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java b/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java index a09018ea39..34a758bf9c 100644 --- a/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java +++ b/src/com/android/settings/bluetooth/AdvancedBluetoothDetailsHeaderController.java @@ -18,16 +18,19 @@ package com.android.settings.bluetooth; 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.bluetooth.CachedBluetoothDevice; 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 mIconCache; private CachedBluetoothDevice mCachedDevice; public AdvancedBluetoothDetailsHeaderController(Context context, String prefKey) { super(context, prefKey); + mIconCache = new HashMap<>(); } @Override @@ -65,6 +77,7 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont super.displayPreference(screen); mLayoutPreference = screen.findPreference(getPreferenceKey()); mLayoutPreference.setVisible(isAvailable()); + refresh(); } @@ -76,6 +89,14 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont @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 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont 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 @@ public class AdvancedBluetoothDetailsHeaderController extends BasePreferenceCont 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 af150527c4..a7fae1441f 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 @@ public class BluetoothDetailsButtonsController extends BluetoothDetailsControlle 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 994daa7860..fd805b828f 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 @@ public class BluetoothDetailsHeaderController extends BluetoothDetailsController } @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 e6462731b9..53fac3ca5f 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 @@ package com.android.settings.bluetooth; 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 @@ public class AdvancedBluetoothDetailsHeaderControllerTest{ 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 @@ public class AdvancedBluetoothDetailsHeaderControllerTest{ 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 930d9cbe08..cf119eaa10 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 @@ 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.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 @@ public class BluetoothDetailsHeaderControllerTest extends BluetoothDetailsContro private LocalBluetoothManager mBluetoothManager; @Mock private CachedBluetoothDeviceManager mCachedDeviceManager; + @Mock + private BluetoothDevice mBluetoothDevice; @Override public void setUp() { @@ -77,6 +73,7 @@ public class BluetoothDetailsHeaderControllerTest extends BluetoothDetailsContro mPreference.setKey(mController.getPreferenceKey()); mScreen.addPreference(mPreference); setupDevice(mDeviceConfig); + when(mCachedDevice.getDevice()).thenReturn(mBluetoothDevice); } @After @@ -124,4 +121,12 @@ public class BluetoothDetailsHeaderControllerTest extends BluetoothDetailsContro 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(); + } } -- 2.11.0