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
This commit is contained in:
Hugh Chen
2021-04-16 22:14:38 +08:00
parent 43dfe44a0f
commit 3a82485618
2 changed files with 101 additions and 1 deletions

View File

@@ -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<CachedBluetoothDevice>
private boolean mIsHearingAidProfileConnectedFail = false;
// Group second device for Hearing Aid
private CachedBluetoothDevice mSubDevice;
@VisibleForTesting
LruCache<String, BitmapDrawable> mDrawableCache;
private final Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
@@ -131,6 +141,19 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
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<String, BitmapDrawable>(cacheSize) {
@Override
protected int sizeOf(String key, BitmapDrawable bitmap) {
return bitmap.getBitmap().getByteCount() / 1024;
}
};
}
/**
@@ -381,6 +404,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice>
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<CachedBluetoothDevice>
}
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<CachedBluetoothDevice>
mSubDevice.mJustDiscovered = tmpJustDiscovered;
fetchActiveDevices();
}
/**
* Get cached bluetooth icon with description
*/
public Pair<Drawable, String> 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();
}
}

View File

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