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); + } }