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

Commit b2674eb5 authored by ykhung's avatar ykhung Committed by YUKAI HUNG
Browse files

Add cache mechanism to improve icon and label loading performance

Bug: 185207505
Test: make SettingsRoboTests
Test: make SettingsGoogleRoboTests
Change-Id: I73dba5e40783f9ef4cfc0c4c33ea56b12754535d
parent 3909ddb7
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -255,7 +255,8 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick
    private int getTrapezoidIndex(float x) {
        for (int index = 0; index < mTrapezoidSlot.length; index++) {
            final TrapezoidSlot slot = mTrapezoidSlot[index];
            if (x >= slot.mLeft && x <= slot.mRight) {
            if (x >= slot.mLeft - mTrapezoidHOffset
                    && x <= slot.mRight + mTrapezoidHOffset) {
                return index;
            }
        }
+41 −9
Original line number Diff line number Diff line
@@ -26,11 +26,18 @@ import androidx.annotation.VisibleForTesting;

import java.time.Duration;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;

/** A container class to carry battery data in a specific time slot. */
public final class BatteryDiffEntry {
    private static final String TAG = "BatteryDiffEntry";

    static Locale sCurrentLocale = null;
    // Caches app label and icon to improve loading performance.
    static final Map<String, BatteryEntry.NameAndIcon> sResourceCache = new HashMap<>();

    /** A comparator for {@link BatteryDiffEntry} based on consumed percentage. */
    public static final Comparator<BatteryDiffEntry> COMPARATOR =
            (a, b) -> Double.compare(b.getPercentOfTotal(), a.getPercentOfTotal());
@@ -97,15 +104,8 @@ public final class BatteryDiffEntry {
    /** Gets the app icon {@link Drawable} for this entry. */
    public Drawable getAppIcon() {
        loadLabelAndIcon();
        if (mBatteryHistEntry.mConsumerType !=
                ConvertUtils.CONSUMER_TYPE_UID_BATTERY) {
        return mAppIcon;
    }
        // Returns default application icon if UID_BATTERY icon is null.
        return mAppIcon == null
            ? mContext.getPackageManager().getDefaultActivityIcon()
            : mAppIcon;
    }

    /** Gets the searching package name for UID battery type. */
    public String getPackageName() {
@@ -140,11 +140,37 @@ public final class BatteryDiffEntry {
                }
                break;
            case ConvertUtils.CONSUMER_TYPE_UID_BATTERY:
                final BatteryEntry.NameAndIcon nameAndIcon = getCache();
                if (nameAndIcon != null) {
                    mAppLabel = nameAndIcon.name;
                    mAppIcon = nameAndIcon.icon;
                    break;
                }
                loadNameAndIconForUid();
                // Uses application default icon if we cannot find it from package.
                if (mAppIcon == null) {
                    mAppIcon = mContext.getPackageManager().getDefaultActivityIcon();
                }
                if (mAppLabel != null && mAppIcon != null) {
                    sResourceCache.put(
                        mBatteryHistEntry.getKey(),
                        new BatteryEntry.NameAndIcon(mAppLabel, mAppIcon, /*iconId=*/ 0));
                }
                break;
        }
    }

    private BatteryEntry.NameAndIcon getCache() {
        final Locale locale = Locale.getDefault();
        if (sCurrentLocale != locale) {
            Log.d(TAG, String.format("clearCache() locale is changed from %s to %s",
                sCurrentLocale, locale));
            sCurrentLocale = locale;
            clearCache();
        }
        return sResourceCache.get(mBatteryHistEntry.getKey());
    }

    private void loadNameAndIconForUid() {
        final String packageName = mBatteryHistEntry.mPackageName;
        final PackageManager packageManager = mContext.getPackageManager();
@@ -153,7 +179,9 @@ public final class BatteryDiffEntry {
            try {
                final ApplicationInfo appInfo =
                    packageManager.getApplicationInfo(packageName, /*no flags*/ 0);
                if (appInfo != null) {
                    mAppLabel = packageManager.getApplicationLabel(appInfo).toString();
                }
            } catch (NameNotFoundException e) {
                Log.e(TAG, "failed to retrieve ApplicationInfo for: " + packageName);
                mAppLabel = packageName;
@@ -203,6 +231,10 @@ public final class BatteryDiffEntry {
        return builder.toString();
    }

    static void clearCache() {
        sResourceCache.clear();
    }

    private static <T> T getNonNull(T originalObj, T newObj) {
        return newObj != null ? newObj : originalObj;
    }
+15 −1
Original line number Diff line number Diff line
@@ -65,6 +65,7 @@ public final class BatteryHistEntry {
    public final int mBatteryStatus;
    public final int mBatteryHealth;

    private String mKey = null;
    private boolean mIsValidEntry = true;

    public BatteryHistEntry(ContentValues values) {
@@ -114,7 +115,20 @@ public final class BatteryHistEntry {

    /** Gets an identifier to represent this {@link BatteryHistEntry}. */
    public String getKey() {
        return mPackageName + "-" + mUserId;
        if (mKey == null) {
            switch (mConsumerType) {
                case ConvertUtils.CONSUMER_TYPE_UID_BATTERY:
                    mKey = Long.toString(mUid);
                    break;
                case ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY:
                    mKey = "S|" + mDrainType;
                    break;
                case ConvertUtils.CONSUMER_TYPE_USER_BATTERY:
                    mKey = "U|" + mUserId;
                    break;
            }
        }
        return mKey;
    }

    @Override
+59 −9
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ package com.android.settings.fuelgauge;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;

@@ -39,6 +40,7 @@ import org.robolectric.RuntimeEnvironment;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;

@RunWith(RobolectricTestRunner.class)
public final class BatteryDiffEntryTest {
@@ -49,6 +51,7 @@ public final class BatteryDiffEntryTest {
    @Mock private PackageManager mockPackageManager;
    @Mock private UserManager mockUserManager;
    @Mock private Drawable mockDrawable;
    @Mock private Drawable mockDrawable2;

    @Before
    public void setUp() {
@@ -56,6 +59,7 @@ public final class BatteryDiffEntryTest {
        mContext = spy(RuntimeEnvironment.application);
        doReturn(mockUserManager).when(mContext).getSystemService(UserManager.class);
        doReturn(mockPackageManager).when(mContext).getPackageManager();
        BatteryDiffEntry.clearCache();
    }

    @Test
@@ -112,6 +116,7 @@ public final class BatteryDiffEntryTest {
        final BatteryDiffEntry entry = createBatteryDiffEntry(10, batteryHistEntry);

        assertThat(entry.getAppLabel()).isEqualTo("Ambient display");
        assertThat(BatteryDiffEntry.sResourceCache).isEmpty();
    }

    @Test
@@ -127,6 +132,7 @@ public final class BatteryDiffEntryTest {

        assertThat(entry.getAppLabel()).isEqualTo("Removed user");
        assertThat(entry.getAppIcon()).isNull();
        assertThat(BatteryDiffEntry.sResourceCache).isEmpty();
    }

    @Test
@@ -146,17 +152,28 @@ public final class BatteryDiffEntryTest {
        final BatteryDiffEntry entry = createBatteryDiffEntry(10, batteryHistEntry);

        assertThat(entry.getAppLabel()).isEqualTo(expectedAppLabel);
        assertThat(BatteryDiffEntry.sResourceCache).hasSize(1);
        // Verifies the app label in the cache.
        final BatteryEntry.NameAndIcon nameAndIcon =
            BatteryDiffEntry.sResourceCache.get(batteryHistEntry.getKey());
        assertThat(nameAndIcon.name).isEqualTo(expectedAppLabel);
    }

    @Test
    public void testGetAppLabel_loadDataFromPreDefinedNameAndUid() {
        final String expectedAppLabel = "Android OS";
        final ContentValues values = getContentValuesWithType(
            ConvertUtils.CONSUMER_TYPE_UID_BATTERY);
        final BatteryHistEntry batteryHistEntry = new BatteryHistEntry(values);

        final BatteryDiffEntry entry = createBatteryDiffEntry(10, batteryHistEntry);

        assertThat(entry.getAppLabel()).isEqualTo("Android OS");
        assertThat(entry.getAppLabel()).isEqualTo(expectedAppLabel);
        assertThat(BatteryDiffEntry.sResourceCache).hasSize(1);
        // Verifies the app label in the cache.
        final BatteryEntry.NameAndIcon nameAndIcon =
            BatteryDiffEntry.sResourceCache.get(batteryHistEntry.getKey());
        assertThat(nameAndIcon.name).isEqualTo(expectedAppLabel);
    }

    @Test
@@ -171,6 +188,7 @@ public final class BatteryDiffEntryTest {

        entry.mIsLoaded = true;
        assertThat(entry.getAppLabel()).isEqualTo(expectedAppLabel);
        assertThat(BatteryDiffEntry.sResourceCache).isEmpty();
    }

    @Test
@@ -184,20 +202,39 @@ public final class BatteryDiffEntryTest {
        entry.mIsLoaded = true;
        entry.mAppIcon = mockDrawable;
        assertThat(entry.getAppIcon()).isEqualTo(mockDrawable);
        assertThat(BatteryDiffEntry.sResourceCache).isEmpty();
    }

    @Test
    public void testGetAppIcon_uidConsumerWithNullIcon_returnDefaultActivityIcon() {
        final ContentValues values = getContentValuesWithType(
            ConvertUtils.CONSUMER_TYPE_UID_BATTERY);
        final BatteryHistEntry batteryHistEntry = new BatteryHistEntry(values);
        doReturn(mockDrawable).when(mockPackageManager).getDefaultActivityIcon();
    public void testGetAppIcon_uidConsumerWithNullIcon_returnDefaultActivityIcon()
            throws Exception {
        final BatteryDiffEntry entry = createBatteryDiffEntry(mockDrawable);

        final BatteryDiffEntry entry = createBatteryDiffEntry(10, batteryHistEntry);

        entry.mIsLoaded = true;
        entry.mAppIcon = null;
        assertThat(entry.getAppIcon()).isEqualTo(mockDrawable);
        assertThat(BatteryDiffEntry.sResourceCache).hasSize(1);
        // Verifies the app label in the cache.
        final BatteryEntry.NameAndIcon nameAndIcon =
            BatteryDiffEntry.sResourceCache.get(entry.mBatteryHistEntry.getKey());
        assertThat(nameAndIcon.icon).isEqualTo(mockDrawable);
    }

    @Test
    public void testClearCache_switchLocale_clearCacheIconAndLabel() throws Exception {
        Locale.setDefault(new Locale("en_US"));
        final BatteryDiffEntry entry1 = createBatteryDiffEntry(mockDrawable);
        assertThat(entry1.getAppIcon()).isEqualTo(mockDrawable);
        // Switch the locale into another one.
        Locale.setDefault(new Locale("zh_TW"));

        final BatteryDiffEntry entry2 = createBatteryDiffEntry(mockDrawable2);

        // We should get new drawable without caching.
        assertThat(entry2.getAppIcon()).isEqualTo(mockDrawable2);
        // Verifies the cache is updated into the new drawable.
        final BatteryEntry.NameAndIcon nameAndIcon =
            BatteryDiffEntry.sResourceCache.get(entry2.mBatteryHistEntry.getKey());
        assertThat(nameAndIcon.icon).isEqualTo(mockDrawable2);
    }

    private BatteryDiffEntry createBatteryDiffEntry(
@@ -217,4 +254,17 @@ public final class BatteryDiffEntryTest {
        values.put("consumerType", Integer.valueOf(consumerType));
        return values;
    }

    private BatteryDiffEntry createBatteryDiffEntry(Drawable drawable) throws Exception {
        final ContentValues values = getContentValuesWithType(
            ConvertUtils.CONSUMER_TYPE_UID_BATTERY);
        values.put("uid", 1001);
        values.put("packageName", "com.a.b.c");
        final BatteryHistEntry batteryHistEntry = new BatteryHistEntry(values);
        doReturn(drawable).when(mockPackageManager).getDefaultActivityIcon();
        doReturn(null).when(mockPackageManager).getApplicationInfo("com.a.b.c", 0);
        doReturn(new String[] {"com.a.b.c"}).when(mockPackageManager)
            .getPackagesForUid(1001);
        return createBatteryDiffEntry(10, batteryHistEntry);
    }
}
+36 −2
Original line number Diff line number Diff line
@@ -137,6 +137,42 @@ public final class BatteryHistEntryTest {
            /*percentOfTotal=*/ 0.3);
    }

    @Test
    public void testGetKey_consumerUidType_returnExpectedString() {
        final ContentValues values = getContentValuesWithType(
            ConvertUtils.CONSUMER_TYPE_UID_BATTERY);
        values.put("uid", 3);
        final BatteryHistEntry batteryHistEntry = new BatteryHistEntry(values);

        assertThat(batteryHistEntry.getKey()).isEqualTo("3");
    }

    @Test
    public void testGetKey_consumerUserType_returnExpectedString() {
        final ContentValues values = getContentValuesWithType(
            ConvertUtils.CONSUMER_TYPE_USER_BATTERY);
        values.put("userId", 2);
        final BatteryHistEntry batteryHistEntry = new BatteryHistEntry(values);

        assertThat(batteryHistEntry.getKey()).isEqualTo("U|2");
    }

    @Test
    public void testGetKey_consumerSystemType_returnExpectedString() {
        final ContentValues values = getContentValuesWithType(
            ConvertUtils.CONSUMER_TYPE_SYSTEM_BATTERY);
        values.put("drainType", 1);
        final BatteryHistEntry batteryHistEntry = new BatteryHistEntry(values);

        assertThat(batteryHistEntry.getKey()).isEqualTo("S|1");
    }

    private static ContentValues getContentValuesWithType(int consumerType) {
        final ContentValues values = new ContentValues();
        values.put("consumerType", Integer.valueOf(consumerType));
        return values;
    }

    private void assertBatteryHistEntry(
        BatteryHistEntry entry, int drainType, double percentOfTotal) {
        assertThat(entry.isValidEntry()).isTrue();
@@ -161,7 +197,5 @@ public final class BatteryHistEntryTest {
            .isEqualTo(BatteryManager.BATTERY_STATUS_FULL);
        assertThat(entry.mBatteryHealth)
            .isEqualTo(BatteryManager.BATTERY_HEALTH_COLD);
        assertThat(entry.getKey())
            .isEqualTo("com.google.android.settings.battery-" + entry.mUserId);
    }
}