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

Commit 3d7b8b3b authored by Zaiyue Xue's avatar Zaiyue Xue
Browse files

[Battery usage U] Show battery usage info immediately up till as close to the...

[Battery usage U] Show battery usage info immediately up till as close to the current moment as possible.

screen_record: https://drive.google.com/file/d/1Wdm8Wpn39k6E9Yo4bbTENf5VANP337QA/view?usp=share_link&resourcekey=0-1LNmaTaZI13DUmjNfkBehQ

Bug: 252407178
Fix: 252407178
Test: maunal
Change-Id: Ia08dea791bb72113719fd1316e8e9587a96eaef1
parent a1e87604
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -4987,6 +4987,8 @@
    <string name="battery_usage_for_background_time">Background: <xliff:g id="time">%s</xliff:g></string>
    <!-- [CHAR_LIMIT=NONE] Battery usage main screen footer for empty content -->
    <string name="battery_usage_screen_footer_empty">Battery usage data will be available in a few hours once fully charged</string>
    <!-- [CHAR_LIMIT=NONE] Battery chart label for the current time. -->
    <string name="battery_usage_chart_label_now">now</string>
    <!-- [CHAR_LIMIT=NONE] Accessibility content description for battery chart view. -->
    <string name="battery_usage_chart">Battery usage chart</string>
    <!-- [CHAR_LIMIT=NONE] Accessibility content description for daily battery chart view. -->
+27 −2
Original line number Diff line number Diff line
@@ -47,6 +47,8 @@ import com.android.settingslib.core.lifecycle.events.OnDestroy;
import com.android.settingslib.core.lifecycle.events.OnResume;
import com.android.settingslib.core.lifecycle.events.OnSaveInstanceState;

import com.google.common.base.Objects;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
@@ -238,7 +240,8 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
                    hourlyBatteryLevelsPerDay.getLevels(),
                    hourlyBatteryLevelsPerDay.getTimestamps(),
                    BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS,
                    mHourlyChartLabelTextGenerator));
                    mHourlyChartLabelTextGenerator.setLatestTimestamp(getLast(getLast(
                            batteryLevelData.getHourlyBatteryLevelsPerDay()).getTimestamps()))));
        }
        refreshUi();
    }
@@ -514,6 +517,13 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
        return allBatteryDiffData == null ? null : allBatteryDiffData.getAppDiffEntryList();
    }

    private static <T> T getLast(List<T> list) {
        if (list == null || list.isEmpty()) {
            return null;
        }
        return list.get(list.size() - 1);
    }

    /** Used for {@link AppBatteryPreferenceController}. */
    public static BatteryDiffEntry getAppBatteryUsageData(
            Context context, String packageName, int userId) {
@@ -553,18 +563,33 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll

    private final class HourlyChartLabelTextGenerator implements
            BatteryChartViewModel.LabelTextGenerator {
        private Long mLatestTimestamp;

        @Override
        public String generateText(List<Long> timestamps, int index) {
            if (Objects.equal(timestamps.get(index), mLatestTimestamp)) {
                // Replaces the latest timestamp text to "now".
                return mContext.getString(R.string.battery_usage_chart_label_now);
            }
            return ConvertUtils.utcToLocalTimeHour(mContext, timestamps.get(index),
                    mIs24HourFormat);
        }

        @Override
        public String generateFullText(List<Long> timestamps, int index) {
            if (Objects.equal(timestamps.get(index), mLatestTimestamp)) {
                // Replaces the latest timestamp text to "now".
                return mContext.getString(R.string.battery_usage_chart_label_now);
            }
            return index == timestamps.size() - 1
                    ? generateText(timestamps, index)
                    : String.format("%s%s%s", generateText(timestamps, index),
                    mIs24HourFormat ? "-" : " - ", generateText(timestamps, index + 1));
        }

        public HourlyChartLabelTextGenerator setLatestTimestamp(Long latestTimestamp) {
            this.mLatestTimestamp = latestTimestamp;
            return this;
        }
    }
}
+70 −19
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static com.android.settings.fuelgauge.batteryusage.ConvertUtils.utcToLoca
import android.app.settings.SettingsEnums;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.BatteryConsumer;
@@ -86,6 +87,12 @@ public final class DataProcessor {
    static final double PERCENTAGE_OF_TOTAL_THRESHOLD = 1f;
    @VisibleForTesting
    static final int SELECTED_INDEX_ALL = BatteryChartViewModel.SELECTED_INDEX_ALL;
    @VisibleForTesting
    static final String CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER =
            "CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER";

    @VisibleForTesting
    static long sFakeCurrentTimeMillis = 0;

    /** A callback listener when battery usage loading async task is executed. */
    public interface UsageMapAsyncResponse {
@@ -239,11 +246,12 @@ public final class DataProcessor {
            return resultMap;
        }
        Collections.sort(rawTimestampList);
        final List<Long> expectedTimestampList = getTimestampSlots(rawTimestampList);
        final long currentTime = getCurrentTimeMillis();
        final List<Long> expectedTimestampList = getTimestampSlots(rawTimestampList, currentTime);
        final boolean isFromFullCharge =
                isFromFullCharge(batteryHistoryMap.get(rawTimestampList.get(0)));
        interpolateHistory(
                context, rawTimestampList, expectedTimestampList, isFromFullCharge,
                context, rawTimestampList, expectedTimestampList, currentTime, isFromFullCharge,
                batteryHistoryMap, resultMap);
        Log.d(TAG, String.format("getHistoryMapWithExpectedTimestamps() size=%d in %d/ms",
                resultMap.size(), (System.currentTimeMillis() - startTime)));
@@ -278,19 +286,16 @@ public final class DataProcessor {
     * between start and end two even hour values.
     */
    @VisibleForTesting
    static List<Long> getTimestampSlots(final List<Long> rawTimestampList) {
    static List<Long> getTimestampSlots(final List<Long> rawTimestampList, final long currentTime) {
        final List<Long> timestampSlots = new ArrayList<>();
        final int rawTimestampListSize = rawTimestampList.size();
        // If timestamp number is smaller than 2, the following computation is not necessary.
        if (rawTimestampListSize < MIN_TIMESTAMP_DATA_SIZE) {
        if (rawTimestampList.isEmpty()) {
            return timestampSlots;
        }
        final long rawStartTimestamp = rawTimestampList.get(0);
        final long rawEndTimestamp = rawTimestampList.get(rawTimestampListSize - 1);
        // No matter the start is from last full charge or 6 days ago, use the nearest even hour.
        final long startTimestamp = getNearestEvenHourTimestamp(rawStartTimestamp);
        // Use the even hour before the raw end timestamp as the end.
        final long endTimestamp = getLastEvenHourBeforeTimestamp(rawEndTimestamp);
        // Use the first even hour after the current time as the end.
        final long endTimestamp = getFirstEvenHourAfterTimestamp(currentTime);
        // If the start timestamp is later or equal the end one, return the empty list.
        if (startTimestamp >= endTimestamp) {
            return timestampSlots;
@@ -451,10 +456,7 @@ public final class DataProcessor {
    @Nullable
    static BatteryDiffData generateBatteryDiffData(
            final Context context,
            @Nullable final List<BatteryEntry> batteryEntryList,
            final BatteryUsageStats batteryUsageStats) {
        final List<BatteryHistEntry> batteryHistEntryList =
                convertToBatteryHistEntry(batteryEntryList, batteryUsageStats);
            final List<BatteryHistEntry> batteryHistEntryList) {
        if (batteryHistEntryList == null || batteryHistEntryList.isEmpty()) {
            Log.w(TAG, "batteryHistEntryList is null or empty in generateBatteryDiffData()");
            return null;
@@ -531,7 +533,7 @@ public final class DataProcessor {
        final Map<Integer, BatteryDiffData> allUsageMap = new HashMap<>();
        // Always construct the map whether the value is null or not.
        allUsageMap.put(SELECTED_INDEX_ALL,
                getBatteryDiffDataFromBatteryStatsService(context));
                generateBatteryDiffData(context, getBatteryHistListFromFromStatsService(context)));
        resultMap.put(SELECTED_INDEX_ALL, allUsageMap);

        // Compute the apps number before purge. Must put before purgeLowPercentageAndFakeData.
@@ -545,24 +547,33 @@ public final class DataProcessor {
    }

    @Nullable
    private static BatteryDiffData getBatteryDiffDataFromBatteryStatsService(
    private static List<BatteryHistEntry> getBatteryHistListFromFromStatsService(
            final Context context) {
        BatteryDiffData batteryDiffData = null;
        List<BatteryHistEntry> batteryHistEntryList = null;
        try {
            final BatteryUsageStats batteryUsageStats = getBatteryUsageStats(context);
            final List<BatteryEntry> batteryEntryList =
                    generateBatteryEntryListFromBatteryUsageStats(context, batteryUsageStats);
            batteryDiffData = generateBatteryDiffData(context, batteryEntryList, batteryUsageStats);
            batteryHistEntryList = convertToBatteryHistEntry(batteryEntryList, batteryUsageStats);
            closeBatteryUsageStats(batteryUsageStats);
        } catch (RuntimeException e) {
            Log.e(TAG, "load batteryUsageStats:" + e);
        }

        return batteryDiffData;
        return batteryHistEntryList;
    }

    private static Map<String, BatteryHistEntry> getCurrentBatteryHistoryMapFromStatsService(
            final Context context) {
        final List<BatteryHistEntry> batteryHistEntryList =
                getBatteryHistListFromFromStatsService(context);
        return batteryHistEntryList == null ? new HashMap<>()
                : batteryHistEntryList.stream().collect(Collectors.toMap(e -> e.getKey(), e -> e));
    }

    @VisibleForTesting
    @Nullable
    private static List<BatteryHistEntry> convertToBatteryHistEntry(
    static List<BatteryHistEntry> convertToBatteryHistEntry(
            @Nullable final List<BatteryEntry> batteryEntryList,
            final BatteryUsageStats batteryUsageStats) {
        if (batteryEntryList == null || batteryEntryList.isEmpty()) {
@@ -591,6 +602,7 @@ public final class DataProcessor {
            Context context,
            final List<Long> rawTimestampList,
            final List<Long> expectedTimestampSlots,
            final long currentTime,
            final boolean isFromFullCharge,
            final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap,
            final Map<Long, Map<String, BatteryHistEntry>> resultMap) {
@@ -611,6 +623,17 @@ public final class DataProcessor {
        final int expectedTimestampSlotsSize = expectedTimestampSlots.size();
        for (int index = startIndex; index < expectedTimestampSlotsSize; index++) {
            final long currentSlot = expectedTimestampSlots.get(index);
            if (currentSlot > currentTime) {
                // The slot timestamp is greater than the current time. Puts a placeholder first,
                // then in the async task, loads the real time battery usage data from the battery
                // stats service.
                // If current time is odd hour, one placeholder is added. If the current hour is
                // even hour, two placeholders are added. This is because the method
                // insertHourlyUsageDiffDataPerSlot() requires continuing three hours data.
                resultMap.put(currentSlot,
                        Map.of(CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER, EMPTY_BATTERY_HIST_ENTRY));
                continue;
            }
            final boolean isStartOrEnd = index == 0 || index == expectedTimestampSlotsSize - 1;
            interpolateHistoryForSlot(
                    context, currentSlot, rawTimestampList, batteryHistoryMap, resultMap,
@@ -732,6 +755,13 @@ public final class DataProcessor {
        return getEvenHourTimestamp(rawTimestamp, /*addHourOfDay*/ 1);
    }

    /**
     * @return Returns the fist even hour timestamp after the given timestamp.
     */
    private static long getFirstEvenHourAfterTimestamp(long rawTimestamp) {
        return getLastEvenHourBeforeTimestamp(rawTimestamp + DateUtils.HOUR_IN_MILLIS * 2);
    }

    /**
     * @return Returns the last even hour timestamp before the given timestamp.
     */
@@ -809,6 +839,12 @@ public final class DataProcessor {
                    + utcToLocalTime(context, timestamp));
            return null;
        }
        // The current time battery history hasn't been loaded yet, returns the current battery
        // level.
        if (entryMap.containsKey(CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER)) {
            final Intent intent = BatteryUtils.getBatteryIntent(context);
            return BatteryStatus.getBatteryLevel(intent);
        }
        // Averages the battery level in each time slot to avoid corner conditions.
        float batteryLevelCounter = 0;
        for (BatteryHistEntry entry : entryMap.values()) {
@@ -1394,6 +1430,10 @@ public final class DataProcessor {
        return batteryDiffEntry;
    }

    private static long getCurrentTimeMillis() {
        return sFakeCurrentTimeMillis > 0 ? sFakeCurrentTimeMillis : System.currentTimeMillis();
    }

    private static void logAppCountMetrics(
            Context context, final int countOfAppBeforePurge, final int countOfAppAfterPurge) {
        context = context.getApplicationContext();
@@ -1465,6 +1505,17 @@ public final class DataProcessor {
                return null;
            }
            final long startTime = System.currentTimeMillis();
            // Loads the current battery usage data from the battery stats service and replaces the
            // placeholder in mBatteryHistoryMap.
            Map<String, BatteryHistEntry> currentBatteryHistoryMap =
                    getCurrentBatteryHistoryMapFromStatsService(mApplicationContext);
            for (Map.Entry<Long, Map<String, BatteryHistEntry>> mapEntry
                    : mBatteryHistoryMap.entrySet()) {
                if (mapEntry.getValue().containsKey(CURRENT_TIME_BATTERY_HISTORY_PLACEHOLDER)) {
                    mapEntry.setValue(currentBatteryHistoryMap);
                }
            }

            final Map<Integer, Map<Integer, BatteryDiffData>> batteryUsageMap =
                    getBatteryUsageMap(
                            mApplicationContext, mHourlyBatteryLevelsPerDay, mBatteryHistoryMap);
+21 −7
Original line number Diff line number Diff line
@@ -19,7 +19,9 @@ package com.android.settings.fuelgauge.batteryusage;
import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atLeastOnce;
@@ -30,7 +32,9 @@ import static org.mockito.Mockito.verify;

import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.os.BatteryManager;
import android.os.Bundle;
import android.os.LocaleList;
import android.text.format.DateUtils;
@@ -57,6 +61,8 @@ import java.util.TimeZone;

@RunWith(RobolectricTestRunner.class)
public final class BatteryChartPreferenceControllerTest {
    @Mock
    private Intent mIntent;
    @Mock
    private SettingsActivity mSettingsActivity;
    @Mock
@@ -90,6 +96,9 @@ public final class BatteryChartPreferenceControllerTest {
                .when(mFeatureFactory.powerUsageFeatureProvider)
                .getHideApplicationEntries(mContext);
        doReturn(mLayoutParams).when(mDailyChartView).getLayoutParams();
        doReturn(mIntent).when(mContext).registerReceiver(any(), any());
        doReturn(100).when(mIntent).getIntExtra(eq(BatteryManager.EXTRA_SCALE), anyInt());
        doReturn(66).when(mIntent).getIntExtra(eq(BatteryManager.EXTRA_LEVEL), anyInt());
        setupHourlyChartViewAnimationMock();
        mBatteryChartPreferenceController = createController();
        mBatteryChartPreferenceController.mPrefContext = mContext;
@@ -144,10 +153,11 @@ public final class BatteryChartPreferenceControllerTest {
        // Ignore fast refresh ui from the data processor callback.
        verify(mHourlyChartView, atLeast(0)).setViewModel(null);
        verify(mHourlyChartView, atLeastOnce()).setViewModel(new BatteryChartViewModel(
                List.of(100, 97, 95),
                List.of(100, 97, 95, 66),
                List.of(1619251200000L /* 8 AM */,
                        1619258400000L /* 10 AM */,
                        1619265600000L /* 12 PM */),
                        1619265600000L /* 12 PM */,
                        1619272800000L /* 2 PM */),
                BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS,
                mBatteryChartPreferenceController.mHourlyChartLabelTextGenerator));
    }
@@ -159,12 +169,12 @@ public final class BatteryChartPreferenceControllerTest {
        setupHourlyChartViewAnimationMock();

        BatteryChartViewModel expectedDailyViewModel = new BatteryChartViewModel(
                List.of(100, 83, 59, 41),
                List.of(100, 83, 59, 66),
                // "Sat", "Sun", "Mon", "Mon"
                List.of(1619251200000L /* Sat */,
                        1619308800000L /* Sun */,
                        1619395200000L /* Mon */,
                        1619460000000L /* Mon */),
                        1619467200000L /* Mon */),
                BatteryChartViewModel.AxisLabelPosition.CENTER_OF_TRAPEZOIDS,
                mBatteryChartPreferenceController.mDailyChartLabelTextGenerator);

@@ -244,7 +254,7 @@ public final class BatteryChartPreferenceControllerTest {
        expectedDailyViewModel.setSelectedIndex(2);
        verify(mDailyChartView).setViewModel(expectedDailyViewModel);
        verify(mHourlyChartView).setViewModel(new BatteryChartViewModel(
                List.of(59, 57, 55, 53, 51, 49, 47, 45, 43, 41),
                List.of(59, 57, 55, 53, 51, 49, 47, 45, 43, 41, 66),
                List.of(1619395200000L /* 12 AM */,
                        1619402400000L /* 2 AM */,
                        1619409600000L /* 4 AM */,
@@ -254,9 +264,11 @@ public final class BatteryChartPreferenceControllerTest {
                        1619438400000L /* 12 PM */,
                        1619445600000L /* 2 PM */,
                        1619452800000L /* 4 PM */,
                        1619460000000L /* 6 PM */),
                        1619460000000L /* 6 PM */,
                        1619467200000L /* 8 PM */),
                BatteryChartViewModel.AxisLabelPosition.BETWEEN_TRAPEZOIDS,
                mBatteryChartPreferenceController.mHourlyChartLabelTextGenerator));

    }

    @Test
@@ -364,7 +376,7 @@ public final class BatteryChartPreferenceControllerTest {
        final int totalHour = BatteryChartPreferenceController.getTotalHours(batteryLevelData);

        // Only calculate the even hours.
        assertThat(totalHour).isEqualTo(58);
        assertThat(totalHour).isEqualTo(60);
    }

    private static Long generateTimestamp(int index) {
@@ -395,6 +407,8 @@ public final class BatteryChartPreferenceControllerTest {
            entryMap.put("fake_entry_key" + index, entry);
            batteryHistoryMap.put(generateTimestamp(index), entryMap);
        }
        DataProcessor.sFakeCurrentTimeMillis =
                generateTimestamp(numOfHours - 1) + DateUtils.MINUTE_IN_MILLIS;
        return batteryHistoryMap;
    }

+47 −21

File changed.

Preview size limit exceeded, changes collapsed.