Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3a824856 authored by Hugh Chen's avatar Hugh Chen
Browse files

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
parent 43dfe44a
Loading
Loading
Loading
Loading
+63 −1
Original line number Diff line number Diff line
@@ -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() {
        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();
    }
}
+38 −0
Original line number Diff line number Diff line
@@ -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);
    }
}