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

Commit 10ae330a authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "Introduce trimMemory to AppIconCacheManager"

parents bd01f346 c301e09c
Loading
Loading
Loading
Loading
+25 −1
Original line number Diff line number Diff line
@@ -22,13 +22,16 @@ import android.os.UserHandle;
import android.util.Log;
import android.util.LruCache;

import androidx.annotation.VisibleForTesting;

/**
 * Cache app icon for management.
 */
public class AppIconCacheManager {
    private static final String TAG = "AppIconCacheManager";
    private static final float CACHE_RATIO = 0.1f;
    private static final int MAX_CACHE_SIZE_IN_KB = getMaxCacheInKb();
    @VisibleForTesting
    static final int MAX_CACHE_SIZE_IN_KB = getMaxCacheInKb();
    private static final String DELIMITER = ":";
    private static AppIconCacheManager sAppIconCacheManager;
    private final LruCache<String, Drawable> mDrawableCache;
@@ -109,4 +112,25 @@ public class AppIconCacheManager {
    private static int getMaxCacheInKb() {
        return Math.round(CACHE_RATIO * Runtime.getRuntime().maxMemory() / 1024);
    }

    /**
     * Clears as much memory as possible.
     *
     * @see android.content.ComponentCallbacks2#onTrimMemory(int)
     */
    public void trimMemory(int level) {
        if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
            // Time to clear everything
            if (sAppIconCacheManager != null) {
                sAppIconCacheManager.mDrawableCache.trimToSize(0);
            }
        } else if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN
                || level == android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) {
            // Tough time but still affordable, clear half of the cache
            if (sAppIconCacheManager != null) {
                final int maxSize = sAppIconCacheManager.mDrawableCache.maxSize();
                sAppIconCacheManager.mDrawableCache.trimToSize(maxSize / 2);
            }
        }
    }
}
+70 −0
Original line number Diff line number Diff line
@@ -34,11 +34,21 @@ import org.robolectric.RobolectricTestRunner;
public class AppIconCacheManagerTest {

    private static final String APP_PACKAGE_NAME = "com.test.app";
    private static final String APP_PACKAGE_NAME1 = "com.test.app1";
    private static final String APP_PACKAGE_NAME2 = "com.test.app2";
    private static final String APP_PACKAGE_NAME3 = "com.test.app3";
    private static final int APP_UID = 9999;

    @Mock
    private Drawable mIcon;

    @Mock
    private Drawable mIcon1;
    @Mock
    private Drawable mIcon2;
    @Mock
    private Drawable mIcon3;

    private AppIconCacheManager mAppIconCacheManager;

    @Before
@@ -48,6 +58,29 @@ public class AppIconCacheManagerTest {
        doReturn(10).when(mIcon).getIntrinsicHeight();
        doReturn(10).when(mIcon).getIntrinsicWidth();
        doReturn(mIcon).when(mIcon).mutate();

        // Algorithm for trim memory test:
        // The real maxsize is defined by AppIconCacheManager.MAX_CACHE_SIZE_IN_KB, and the size
        // of each element is calculated as following:
        // n * n * 4 / 1024
        // In the testcase, we want to mock the maxsize of LruCache is 3, so the formula calculating
        // the size of each element will be like:
        // n * n * 4 / 1024 = maxsize / 3
        // Thus, n = square_root(maxsize / 3 * 1024 / 4), which can be used as an icon size.
        final int iconSize =
                (int) Math.sqrt(AppIconCacheManager.MAX_CACHE_SIZE_IN_KB / 3f * 1024f / 4f);

        doReturn(iconSize).when(mIcon1).getIntrinsicHeight();
        doReturn(iconSize).when(mIcon1).getIntrinsicWidth();
        doReturn(mIcon1).when(mIcon1).mutate();

        doReturn(iconSize).when(mIcon2).getIntrinsicHeight();
        doReturn(iconSize).when(mIcon2).getIntrinsicWidth();
        doReturn(mIcon2).when(mIcon2).mutate();

        doReturn(iconSize).when(mIcon3).getIntrinsicHeight();
        doReturn(iconSize).when(mIcon3).getIntrinsicWidth();
        doReturn(mIcon3).when(mIcon3).mutate();
    }

    @After
@@ -106,4 +139,41 @@ public class AppIconCacheManagerTest {

        assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNull();
    }

    @Test
    public void trimMemory_levelSatisfied_shouldNotCacheIcon() {

        mAppIconCacheManager.put(APP_PACKAGE_NAME1, APP_UID, mIcon1);
        mAppIconCacheManager.put(APP_PACKAGE_NAME2, APP_UID, mIcon2);
        mAppIconCacheManager.put(APP_PACKAGE_NAME3, APP_UID, mIcon3);

        // Expected to trim size to 0
        final int level = android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
        mAppIconCacheManager.trimMemory(level);

        // None of the elements should be cached
        assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME1, APP_UID)).isNull();
        assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME2, APP_UID)).isNull();
        assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME3, APP_UID)).isNull();
    }

    @Test
    public void trimMemory_levelSatisfied_shouldCacheAtLeastHalf() {

        mAppIconCacheManager.put(APP_PACKAGE_NAME1, APP_UID, mIcon1);
        mAppIconCacheManager.put(APP_PACKAGE_NAME2, APP_UID, mIcon2);
        mAppIconCacheManager.put(APP_PACKAGE_NAME3, APP_UID, mIcon3);

        // Get the last element
        mAppIconCacheManager.get(APP_PACKAGE_NAME1, APP_UID);

        // Expected to trim size to half of it, which is int( 3 / 2 ) = 1
        final int level = android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL;
        mAppIconCacheManager.trimMemory(level);

        // There should be only one cached element, which is the last recently used one
        assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME1, APP_UID)).isNotNull();
        assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME2, APP_UID)).isNull();
        assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME3, APP_UID)).isNull();
    }
}