From 3a82485618c13de1bd6fc80440bd715865ae6ae6 Mon Sep 17 00:00:00 2001 From: Hugh Chen Date: Fri, 16 Apr 2021 22:14:38 +0800 Subject: [PATCH] Use LruCache to cache advanced bluetooth deivce icon This CL uses LruCache to cache advanced bluetooth device icons. It can reduce the frequency to access content providers to get advanced device icons since the advanced device icons would not changed. Bug: 178255374 Test: make RunSettingsLibRoboTests -j56 Change-Id: I9c8957b5f78ff5047fa4b85fc37a5e27b731465d --- .../bluetooth/CachedBluetoothDevice.java | 64 ++++++++++++++++++- .../bluetooth/CachedBluetoothDeviceTest.java | 38 +++++++++++ 2 files changed, 101 insertions(+), 1 deletion(-) diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index 9c7aac1355716..4558a8a3d33ee 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -24,6 +24,10 @@ import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothUuid; import android.content.Context; import android.content.SharedPreferences; +import android.content.res.Resources; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.net.Uri; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -32,12 +36,16 @@ import android.os.SystemClock; import android.text.TextUtils; import android.util.EventLog; import android.util.Log; +import android.util.LruCache; +import android.util.Pair; import androidx.annotation.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.settingslib.R; import com.android.settingslib.Utils; +import com.android.settingslib.utils.ThreadUtils; +import com.android.settingslib.widget.AdaptiveOutlineDrawable; import java.util.ArrayList; import java.util.Collection; @@ -100,6 +108,8 @@ public class CachedBluetoothDevice implements Comparable private boolean mIsHearingAidProfileConnectedFail = false; // Group second device for Hearing Aid private CachedBluetoothDevice mSubDevice; + @VisibleForTesting + LruCache mDrawableCache; private final Handler mHandler = new Handler(Looper.getMainLooper()) { @Override @@ -131,6 +141,19 @@ public class CachedBluetoothDevice implements Comparable mDevice = device; fillData(); mHiSyncId = BluetoothHearingAid.HI_SYNC_ID_INVALID; + initDrawableCache(); + } + + private void initDrawableCache() { + int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); + int cacheSize = maxMemory / 8; + + mDrawableCache = new LruCache(cacheSize) { + @Override + protected int sizeOf(String key, BitmapDrawable bitmap) { + return bitmap.getBitmap().getByteCount() / 1024; + } + }; } /** @@ -381,6 +404,7 @@ public class CachedBluetoothDevice implements Comparable if (dev != null) { final boolean successful = dev.removeBond(); if (successful) { + releaseLruCache(); if (BluetoothUtils.D) { Log.d(TAG, "Command sent successfully:REMOVE_BOND " + describe(null)); } @@ -500,7 +524,21 @@ public class CachedBluetoothDevice implements Comparable } void refresh() { - dispatchAttributesChanged(); + ThreadUtils.postOnBackgroundThread(() -> { + if (BluetoothUtils.isAdvancedDetailsHeader(mDevice)) { + Uri uri = BluetoothUtils.getUriMetaData(getDevice(), + BluetoothDevice.METADATA_MAIN_ICON); + if (uri != null && mDrawableCache.get(uri.toString()) == null) { + mDrawableCache.put(uri.toString(), + (BitmapDrawable) BluetoothUtils.getBtDrawableWithDescription( + mContext, this).first); + } + } + + ThreadUtils.postOnMainThread(() -> { + dispatchAttributesChanged(); + }); + }); } public void setJustDiscovered(boolean justDiscovered) { @@ -1178,4 +1216,28 @@ public class CachedBluetoothDevice implements Comparable mSubDevice.mJustDiscovered = tmpJustDiscovered; fetchActiveDevices(); } + + /** + * Get cached bluetooth icon with description + */ + public Pair getDrawableWithDescription() { + Uri uri = BluetoothUtils.getUriMetaData(mDevice, BluetoothDevice.METADATA_MAIN_ICON); + if (BluetoothUtils.isAdvancedDetailsHeader(mDevice) && uri != null) { + BitmapDrawable drawable = mDrawableCache.get(uri.toString()); + if (drawable != null) { + Resources resources = mContext.getResources(); + return new Pair<>(new AdaptiveOutlineDrawable( + resources, drawable.getBitmap()), + BluetoothUtils.getBtClassDrawableWithDescription(mContext, this).second); + } + + refresh(); + } + + return BluetoothUtils.getBtRainbowDrawableWithDescription(mContext, this); + } + + void releaseLruCache() { + mDrawableCache.evictAll(); + } } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java index 53a99ab8cbe44..38172f7600a68 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java @@ -35,6 +35,7 @@ import android.media.AudioManager; import com.android.settingslib.R; import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter; +import com.android.settingslib.widget.AdaptiveOutlineDrawable; import org.junit.Before; import org.junit.Test; @@ -957,4 +958,41 @@ public class CachedBluetoothDeviceTest { // Should not crash } + + @Test + public void getDrawableWithDescription_isAdvancedDevice_returnAdvancedIcon() { + when(mDevice.getMetadata(BluetoothDevice.METADATA_MAIN_ICON)) + .thenReturn("fake_uri".getBytes()); + when(mDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) + .thenReturn("true".getBytes()); + + mCachedDevice.refresh(); + + assertThat(mCachedDevice.getDrawableWithDescription().first).isInstanceOf( + AdaptiveOutlineDrawable.class); + } + + @Test + public void getDrawableWithDescription_isNotAdvancedDevice_returnBluetoothIcon() { + when(mDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) + .thenReturn("false".getBytes()); + + mCachedDevice.refresh(); + + assertThat(mCachedDevice.getDrawableWithDescription().first).isNotInstanceOf( + AdaptiveOutlineDrawable.class); + } + + @Test + public void releaseLruCache_lruCacheShouldBeRelease() { + when(mDevice.getMetadata(BluetoothDevice.METADATA_MAIN_ICON)) + .thenReturn("fake_uri".getBytes()); + when(mDevice.getMetadata(BluetoothDevice.METADATA_IS_UNTETHERED_HEADSET)) + .thenReturn("true".getBytes()); + + mCachedDevice.refresh(); + mCachedDevice.releaseLruCache(); + + assertThat(mCachedDevice.mDrawableCache.size()).isEqualTo(0); + } }