Loading src/com/android/settings/fuelgauge/batteryusage/BatteryLevelData.java +34 −26 Original line number Diff line number Diff line Loading @@ -16,58 +16,66 @@ package com.android.settings.fuelgauge.batteryusage; import android.util.Log; import com.google.common.collect.ImmutableList; import androidx.annotation.NonNull; import androidx.core.util.Preconditions; import java.util.List; /** Wraps the battery timestamp and level data used for battery usage chart. */ public final class BatteryLevelData { /** A container for the battery timestamp and level data. */ public static final class PeriodBatteryLevelData { private static final String TAG = "PeriodBatteryLevelData"; private final ImmutableList<Long> mTimestamps; private final ImmutableList<Integer> mLevels; // The length of mTimestamps and mLevels must be the same. mLevels[index] might be null when // there is no level data for the corresponding timestamp. private final List<Long> mTimestamps; private final List<Integer> mLevels; public PeriodBatteryLevelData(List<Long> timestamps, List<Integer> levels) { if (timestamps.size() != levels.size()) { Log.e(TAG, "Different sizes of timestamps and levels. Timestamp: " + timestamps.size() + ", Level: " + levels.size()); mTimestamps = ImmutableList.of(); mLevels = ImmutableList.of(); return; } mTimestamps = ImmutableList.copyOf(timestamps); mLevels = ImmutableList.copyOf(levels); public PeriodBatteryLevelData( @NonNull List<Long> timestamps, @NonNull List<Integer> levels) { Preconditions.checkArgument(timestamps.size() == levels.size(), /* errorMessage= */ "Timestamp: " + timestamps.size() + ", Level: " + levels.size()); mTimestamps = timestamps; mLevels = levels; } public ImmutableList<Long> getTimestamps() { public List<Long> getTimestamps() { return mTimestamps; } public ImmutableList<Integer> getLevels() { public List<Integer> getLevels() { return mLevels; } } /** * There could be 2 cases for the daily battery levels: * 1) length is 2: The usage data is within 1 day. Only contains start and end data, such as * data of 2022-01-01 06:00 and 2022-01-01 16:00. * 2) length > 2: The usage data is more than 1 days. The data should be the start, end and 0am * data of every day between the start and end, such as data of 2022-01-01 06:00, * 2022-01-02 00:00, 2022-01-03 00:00 and 2022-01-03 08:00. */ private final PeriodBatteryLevelData mDailyBatteryLevels; private final ImmutableList<PeriodBatteryLevelData> mHourlyBatteryLevelsPerDay; // The size of hourly data must be the size of daily data - 1. private final List<PeriodBatteryLevelData> mHourlyBatteryLevelsPerDay; public BatteryLevelData( PeriodBatteryLevelData dailyBatteryLevels, List<PeriodBatteryLevelData> hourlyBatteryLevelsPerDay) { @NonNull PeriodBatteryLevelData dailyBatteryLevels, @NonNull List<PeriodBatteryLevelData> hourlyBatteryLevelsPerDay) { final long dailySize = dailyBatteryLevels.getTimestamps().size(); final long hourlySize = hourlyBatteryLevelsPerDay.size(); Preconditions.checkArgument(hourlySize == dailySize - 1, /* errorMessage= */ "DailySize: " + dailySize + ", HourlySize: " + hourlySize); mDailyBatteryLevels = dailyBatteryLevels; mHourlyBatteryLevelsPerDay = ImmutableList.copyOf(hourlyBatteryLevelsPerDay); mHourlyBatteryLevelsPerDay = hourlyBatteryLevelsPerDay; } public PeriodBatteryLevelData getDailyBatteryLevels() { return mDailyBatteryLevels; } public ImmutableList<PeriodBatteryLevelData> getHourlyBatteryLevelsPerDay() { public List<PeriodBatteryLevelData> getHourlyBatteryLevelsPerDay() { return mHourlyBatteryLevelsPerDay; } } No newline at end of file src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java 0 → 100644 +468 −0 File added.Preview size limit exceeded, changes collapsed. Show changes tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java 0 → 100644 +454 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.fuelgauge.batteryusage; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.spy; import android.content.ContentValues; import android.content.Context; import android.text.format.DateUtils; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TimeZone; @RunWith(RobolectricTestRunner.class) public class DataProcessorTest { private static final String FAKE_ENTRY_KEY = "fake_entry_key"; private Context mContext; @Before public void setUp() { MockitoAnnotations.initMocks(this); TimeZone.setDefault(TimeZone.getTimeZone("GMT+8")); mContext = spy(RuntimeEnvironment.application); } @Test public void getBatteryLevelData_emptyHistoryMap_returnNull() { assertThat(DataProcessor.getBatteryLevelData(mContext, null)).isNull(); assertThat(DataProcessor.getBatteryLevelData(mContext, new HashMap<>())).isNull(); } @Test public void getBatteryLevelData_notEnoughData_returnNull() { // The timestamps are within 1 hour. final long[] timestamps = {1000000L, 2000000L, 3000000L}; final int[] levels = {100, 99, 98}; final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = createHistoryMap(timestamps, levels); assertThat(DataProcessor.getBatteryLevelData(mContext, batteryHistoryMap)).isNull(); } @Test public void getBatteryLevelData_returnExpectedResult() { // Timezone GMT+8: 2022-01-01 00:00:00, 2022-01-01 01:00:00, 2022-01-01 02:00:00 final long[] timestamps = {1640966400000L, 1640970000000L, 1640973600000L}; final int[] levels = {100, 99, 98}; final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = createHistoryMap(timestamps, levels); final BatteryLevelData resultData = DataProcessor.getBatteryLevelData(mContext, batteryHistoryMap); final List<Long> expectedDailyTimestamps = List.of(timestamps[0], timestamps[2]); final List<Integer> expectedDailyLevels = List.of(levels[0], levels[2]); final List<List<Long>> expectedHourlyTimestamps = List.of(expectedDailyTimestamps); final List<List<Integer>> expectedHourlyLevels = List.of(expectedDailyLevels); verifyExpectedBatteryLevelData( resultData, expectedDailyTimestamps, expectedDailyLevels, expectedHourlyTimestamps, expectedHourlyLevels); } @Test public void getHistoryMapWithExpectedTimestamps_emptyHistoryMap_returnEmptyMap() { assertThat(DataProcessor .getHistoryMapWithExpectedTimestamps(mContext, new HashMap<>())) .isEmpty(); } @Test public void getHistoryMapWithExpectedTimestamps_returnExpectedMap() { // Timezone GMT+8 final long[] timestamps = { 1640966700000L, // 2022-01-01 00:05:00 1640970180000L, // 2022-01-01 01:03:00 1640973840000L, // 2022-01-01 02:04:00 1640978100000L, // 2022-01-01 03:15:00 1640981400000L // 2022-01-01 04:10:00 }; final int[] levels = {100, 94, 90, 82, 50}; final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = createHistoryMap(timestamps, levels); final Map<Long, Map<String, BatteryHistEntry>> resultMap = DataProcessor.getHistoryMapWithExpectedTimestamps(mContext, batteryHistoryMap); // Timezone GMT+8 final long[] expectedTimestamps = { 1640966400000L, // 2022-01-01 00:00:00 1640970000000L, // 2022-01-01 01:00:00 1640973600000L, // 2022-01-01 02:00:00 1640977200000L, // 2022-01-01 03:00:00 1640980800000L // 2022-01-01 04:00:00 }; final int[] expectedLevels = {100, 94, 90, 84, 56}; assertThat(resultMap).hasSize(expectedLevels.length); for (int index = 0; index < expectedLevels.length; index++) { assertThat(resultMap.get(expectedTimestamps[index]).get(FAKE_ENTRY_KEY).mBatteryLevel) .isEqualTo(expectedLevels[index]); } } @Test public void getLevelDataThroughProcessedHistoryMap_notEnoughData_returnNull() { final long[] timestamps = {100L}; final int[] levels = {100}; final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = createHistoryMap(timestamps, levels); assertThat( DataProcessor.getLevelDataThroughProcessedHistoryMap(mContext, batteryHistoryMap)) .isNull(); } @Test public void getLevelDataThroughProcessedHistoryMap_OneDayData_returnExpectedResult() { // Timezone GMT+8 final long[] timestamps = { 1640966400000L, // 2022-01-01 00:00:00 1640970000000L, // 2022-01-01 01:00:00 1640973600000L, // 2022-01-01 02:00:00 1640977200000L, // 2022-01-01 03:00:00 1640980800000L // 2022-01-01 04:00:00 }; final int[] levels = {100, 94, 90, 82, 50}; final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = createHistoryMap(timestamps, levels); final BatteryLevelData resultData = DataProcessor.getLevelDataThroughProcessedHistoryMap(mContext, batteryHistoryMap); final List<Long> expectedDailyTimestamps = List.of(timestamps[0], timestamps[4]); final List<Integer> expectedDailyLevels = List.of(levels[0], levels[4]); final List<List<Long>> expectedHourlyTimestamps = List.of( List.of(timestamps[0], timestamps[2], timestamps[4]) ); final List<List<Integer>> expectedHourlyLevels = List.of( List.of(levels[0], levels[2], levels[4]) ); verifyExpectedBatteryLevelData( resultData, expectedDailyTimestamps, expectedDailyLevels, expectedHourlyTimestamps, expectedHourlyLevels); } @Test public void getLevelDataThroughProcessedHistoryMap_MultipleDaysData_returnExpectedResult() { // Timezone GMT+8 final long[] timestamps = { 1641038400000L, // 2022-01-01 20:00:00 1641060000000L, // 2022-01-02 02:00:00 1641067200000L, // 2022-01-02 04:00:00 1641081600000L, // 2022-01-02 08:00:00 }; final int[] levels = {100, 94, 90, 82}; final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = createHistoryMap(timestamps, levels); final BatteryLevelData resultData = DataProcessor.getLevelDataThroughProcessedHistoryMap(mContext, batteryHistoryMap); final List<Long> expectedDailyTimestamps = List.of( 1641038400000L, // 2022-01-01 20:00:00 1641052800000L, // 2022-01-02 00:00:00 1641081600000L // 2022-01-02 08:00:00 ); final List<Integer> expectedDailyLevels = new ArrayList<>(); expectedDailyLevels.add(100); expectedDailyLevels.add(null); expectedDailyLevels.add(82); final List<List<Long>> expectedHourlyTimestamps = List.of( List.of( 1641038400000L, // 2022-01-01 20:00:00 1641045600000L, // 2022-01-01 22:00:00 1641052800000L // 2022-01-02 00:00:00 ), List.of( 1641052800000L, // 2022-01-02 00:00:00 1641060000000L, // 2022-01-02 02:00:00 1641067200000L, // 2022-01-02 04:00:00 1641074400000L, // 2022-01-02 06:00:00 1641081600000L // 2022-01-02 08:00:00 ) ); final List<Integer> expectedHourlyLevels1 = new ArrayList<>(); expectedHourlyLevels1.add(100); expectedHourlyLevels1.add(null); expectedHourlyLevels1.add(null); final List<Integer> expectedHourlyLevels2 = new ArrayList<>(); expectedHourlyLevels2.add(null); expectedHourlyLevels2.add(94); expectedHourlyLevels2.add(90); expectedHourlyLevels2.add(null); expectedHourlyLevels2.add(82); final List<List<Integer>> expectedHourlyLevels = List.of( expectedHourlyLevels1, expectedHourlyLevels2 ); verifyExpectedBatteryLevelData( resultData, expectedDailyTimestamps, expectedDailyLevels, expectedHourlyTimestamps, expectedHourlyLevels); } @Test public void getTimestampSlots_emptyRawList_returnEmptyList() { final List<Long> resultList = DataProcessor.getTimestampSlots(new ArrayList<>()); assertThat(resultList).isEmpty(); } @Test public void getTimestampSlots_startWithEvenHour_returnExpectedResult() { final Calendar startCalendar = Calendar.getInstance(); startCalendar.set(2022, 6, 5, 6, 30, 50); // 2022-07-05 06:30:50 final Calendar endCalendar = Calendar.getInstance(); endCalendar.set(2022, 6, 5, 22, 30, 50); // 2022-07-05 22:30:50 final Calendar expectedStartCalendar = Calendar.getInstance(); expectedStartCalendar.set(2022, 6, 5, 6, 0, 0); // 2022-07-05 06:00:00 final Calendar expectedEndCalendar = Calendar.getInstance(); expectedEndCalendar.set(2022, 6, 5, 22, 0, 0); // 2022-07-05 22:00:00 verifyExpectedTimestampSlots( startCalendar, endCalendar, expectedStartCalendar, expectedEndCalendar); } @Test public void getTimestampSlots_startWithOddHour_returnExpectedResult() { final Calendar startCalendar = Calendar.getInstance(); startCalendar.set(2022, 6, 5, 5, 0, 50); // 2022-07-05 05:00:50 final Calendar endCalendar = Calendar.getInstance(); endCalendar.set(2022, 6, 6, 21, 00, 50); // 2022-07-06 21:00:50 final Calendar expectedStartCalendar = Calendar.getInstance(); expectedStartCalendar.set(2022, 6, 5, 6, 00, 00); // 2022-07-05 06:00:00 final Calendar expectedEndCalendar = Calendar.getInstance(); expectedEndCalendar.set(2022, 6, 6, 20, 00, 00); // 2022-07-06 20:00:00 verifyExpectedTimestampSlots( startCalendar, endCalendar, expectedStartCalendar, expectedEndCalendar); } @Test public void getDailyTimestamps_notEnoughData_returnEmptyList() { assertThat(DataProcessor.getDailyTimestamps(new ArrayList<>())).isEmpty(); assertThat(DataProcessor.getDailyTimestamps(List.of(100L))).isEmpty(); } @Test public void getDailyTimestamps_OneDayData_returnExpectedList() { // Timezone GMT+8 final List<Long> timestamps = List.of( 1640966400000L, // 2022-01-01 00:00:00 1640970000000L, // 2022-01-01 01:00:00 1640973600000L, // 2022-01-01 02:00:00 1640977200000L, // 2022-01-01 03:00:00 1640980800000L // 2022-01-01 04:00:00 ); final List<Long> expectedTimestamps = List.of( 1640966400000L, // 2022-01-01 00:00:00 1640980800000L // 2022-01-01 04:00:00 ); assertThat(DataProcessor.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps); } @Test public void getDailyTimestamps_MultipleDaysData_returnExpectedList() { // Timezone GMT+8 final List<Long> timestamps = List.of( 1640988000000L, // 2022-01-01 06:00:00 1641060000000L, // 2022-01-02 02:00:00 1641160800000L, // 2022-01-03 06:00:00 1641254400000L // 2022-01-04 08:00:00 ); final List<Long> expectedTimestamps = List.of( 1640988000000L, // 2022-01-01 06:00:00 1641052800000L, // 2022-01-02 00:00:00 1641139200000L, // 2022-01-03 00:00:00 1641225600000L, // 2022-01-04 00:00:00 1641254400000L // 2022-01-04 08:00:00 ); assertThat(DataProcessor.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps); } @Test public void isFromFullCharge_emptyData_returnFalse() { assertThat(DataProcessor.isFromFullCharge(null)).isFalse(); assertThat(DataProcessor.isFromFullCharge(new HashMap<>())).isFalse(); } @Test public void isFromFullCharge_notChargedData_returnFalse() { final Map<String, BatteryHistEntry> entryMap = new HashMap<>(); final ContentValues values = new ContentValues(); values.put("batteryLevel", 98); final BatteryHistEntry entry = new BatteryHistEntry(values); entryMap.put(FAKE_ENTRY_KEY, entry); assertThat(DataProcessor.isFromFullCharge(entryMap)).isFalse(); } @Test public void isFromFullCharge_chargedData_returnTrue() { final Map<String, BatteryHistEntry> entryMap = new HashMap<>(); final ContentValues values = new ContentValues(); values.put("batteryLevel", 100); final BatteryHistEntry entry = new BatteryHistEntry(values); entryMap.put(FAKE_ENTRY_KEY, entry); assertThat(DataProcessor.isFromFullCharge(entryMap)).isTrue(); } @Test public void findNearestTimestamp_returnExpectedResult() { long[] results = DataProcessor.findNearestTimestamp( Arrays.asList(10L, 20L, 30L, 40L), /*target=*/ 15L); assertThat(results).isEqualTo(new long[] {10L, 20L}); results = DataProcessor.findNearestTimestamp( Arrays.asList(10L, 20L, 30L, 40L), /*target=*/ 10L); assertThat(results).isEqualTo(new long[] {10L, 10L}); results = DataProcessor.findNearestTimestamp( Arrays.asList(10L, 20L, 30L, 40L), /*target=*/ 5L); assertThat(results).isEqualTo(new long[] {0L, 10L}); results = DataProcessor.findNearestTimestamp( Arrays.asList(10L, 20L, 30L, 40L), /*target=*/ 50L); assertThat(results).isEqualTo(new long[] {40L, 0L}); } @Test public void getTimestampOfNextDay_returnExpectedResult() { // 2021-02-28 06:00:00 => 2021-03-01 00:00:00 assertThat(DataProcessor.getTimestampOfNextDay(1614463200000L)) .isEqualTo(1614528000000L); // 2021-12-31 16:00:00 => 2022-01-01 00:00:00 assertThat(DataProcessor.getTimestampOfNextDay(1640937600000L)) .isEqualTo(1640966400000L); } private static Map<Long, Map<String, BatteryHistEntry>> createHistoryMap( final long[] timestamps, final int[] levels) { final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = new HashMap<>(); for (int index = 0; index < timestamps.length; index++) { final Map<String, BatteryHistEntry> entryMap = new HashMap<>(); final ContentValues values = new ContentValues(); values.put(BatteryHistEntry.KEY_BATTERY_LEVEL, levels[index]); final BatteryHistEntry entry = new BatteryHistEntry(values); entryMap.put(FAKE_ENTRY_KEY, entry); batteryHistoryMap.put(timestamps[index], entryMap); } return batteryHistoryMap; } private static void verifyExpectedBatteryLevelData( final BatteryLevelData resultData, final List<Long> expectedDailyTimestamps, final List<Integer> expectedDailyLevels, final List<List<Long>> expectedHourlyTimestamps, final List<List<Integer>> expectedHourlyLevels) { final BatteryLevelData.PeriodBatteryLevelData dailyResultData = resultData.getDailyBatteryLevels(); final List<BatteryLevelData.PeriodBatteryLevelData> hourlyResultData = resultData.getHourlyBatteryLevelsPerDay(); verifyExpectedDailyBatteryLevelData( dailyResultData, expectedDailyTimestamps, expectedDailyLevels); verifyExpectedHourlyBatteryLevelData( hourlyResultData, expectedHourlyTimestamps, expectedHourlyLevels); } private static void verifyExpectedDailyBatteryLevelData( final BatteryLevelData.PeriodBatteryLevelData dailyResultData, final List<Long> expectedDailyTimestamps, final List<Integer> expectedDailyLevels) { assertThat(dailyResultData.getTimestamps()).isEqualTo(expectedDailyTimestamps); assertThat(dailyResultData.getLevels()).isEqualTo(expectedDailyLevels); } private static void verifyExpectedHourlyBatteryLevelData( final List<BatteryLevelData.PeriodBatteryLevelData> hourlyResultData, final List<List<Long>> expectedHourlyTimestamps, final List<List<Integer>> expectedHourlyLevels) { final int expectedHourlySize = expectedHourlyTimestamps.size(); assertThat(hourlyResultData).hasSize(expectedHourlySize); for (int dailyIndex = 0; dailyIndex < expectedHourlySize; dailyIndex++) { assertThat(hourlyResultData.get(dailyIndex).getTimestamps()) .isEqualTo(expectedHourlyTimestamps.get(dailyIndex)); assertThat(hourlyResultData.get(dailyIndex).getLevels()) .isEqualTo(expectedHourlyLevels.get(dailyIndex)); } } private static void verifyExpectedTimestampSlots( final Calendar start, final Calendar end, final Calendar expectedStart, final Calendar expectedEnd) { expectedStart.set(Calendar.MILLISECOND, 0); expectedEnd.set(Calendar.MILLISECOND, 0); final ArrayList<Long> timestampSlots = new ArrayList<>(); timestampSlots.add(start.getTimeInMillis()); timestampSlots.add(end.getTimeInMillis()); final List<Long> resultList = DataProcessor.getTimestampSlots(timestampSlots); for (int index = 0; index < resultList.size(); index++) { final long expectedTimestamp = expectedStart.getTimeInMillis() + index * DateUtils.HOUR_IN_MILLIS; assertThat(resultList.get(index)).isEqualTo(expectedTimestamp); } assertThat(resultList.get(resultList.size() - 1)) .isEqualTo(expectedEnd.getTimeInMillis()); } } Loading
src/com/android/settings/fuelgauge/batteryusage/BatteryLevelData.java +34 −26 Original line number Diff line number Diff line Loading @@ -16,58 +16,66 @@ package com.android.settings.fuelgauge.batteryusage; import android.util.Log; import com.google.common.collect.ImmutableList; import androidx.annotation.NonNull; import androidx.core.util.Preconditions; import java.util.List; /** Wraps the battery timestamp and level data used for battery usage chart. */ public final class BatteryLevelData { /** A container for the battery timestamp and level data. */ public static final class PeriodBatteryLevelData { private static final String TAG = "PeriodBatteryLevelData"; private final ImmutableList<Long> mTimestamps; private final ImmutableList<Integer> mLevels; // The length of mTimestamps and mLevels must be the same. mLevels[index] might be null when // there is no level data for the corresponding timestamp. private final List<Long> mTimestamps; private final List<Integer> mLevels; public PeriodBatteryLevelData(List<Long> timestamps, List<Integer> levels) { if (timestamps.size() != levels.size()) { Log.e(TAG, "Different sizes of timestamps and levels. Timestamp: " + timestamps.size() + ", Level: " + levels.size()); mTimestamps = ImmutableList.of(); mLevels = ImmutableList.of(); return; } mTimestamps = ImmutableList.copyOf(timestamps); mLevels = ImmutableList.copyOf(levels); public PeriodBatteryLevelData( @NonNull List<Long> timestamps, @NonNull List<Integer> levels) { Preconditions.checkArgument(timestamps.size() == levels.size(), /* errorMessage= */ "Timestamp: " + timestamps.size() + ", Level: " + levels.size()); mTimestamps = timestamps; mLevels = levels; } public ImmutableList<Long> getTimestamps() { public List<Long> getTimestamps() { return mTimestamps; } public ImmutableList<Integer> getLevels() { public List<Integer> getLevels() { return mLevels; } } /** * There could be 2 cases for the daily battery levels: * 1) length is 2: The usage data is within 1 day. Only contains start and end data, such as * data of 2022-01-01 06:00 and 2022-01-01 16:00. * 2) length > 2: The usage data is more than 1 days. The data should be the start, end and 0am * data of every day between the start and end, such as data of 2022-01-01 06:00, * 2022-01-02 00:00, 2022-01-03 00:00 and 2022-01-03 08:00. */ private final PeriodBatteryLevelData mDailyBatteryLevels; private final ImmutableList<PeriodBatteryLevelData> mHourlyBatteryLevelsPerDay; // The size of hourly data must be the size of daily data - 1. private final List<PeriodBatteryLevelData> mHourlyBatteryLevelsPerDay; public BatteryLevelData( PeriodBatteryLevelData dailyBatteryLevels, List<PeriodBatteryLevelData> hourlyBatteryLevelsPerDay) { @NonNull PeriodBatteryLevelData dailyBatteryLevels, @NonNull List<PeriodBatteryLevelData> hourlyBatteryLevelsPerDay) { final long dailySize = dailyBatteryLevels.getTimestamps().size(); final long hourlySize = hourlyBatteryLevelsPerDay.size(); Preconditions.checkArgument(hourlySize == dailySize - 1, /* errorMessage= */ "DailySize: " + dailySize + ", HourlySize: " + hourlySize); mDailyBatteryLevels = dailyBatteryLevels; mHourlyBatteryLevelsPerDay = ImmutableList.copyOf(hourlyBatteryLevelsPerDay); mHourlyBatteryLevelsPerDay = hourlyBatteryLevelsPerDay; } public PeriodBatteryLevelData getDailyBatteryLevels() { return mDailyBatteryLevels; } public ImmutableList<PeriodBatteryLevelData> getHourlyBatteryLevelsPerDay() { public List<PeriodBatteryLevelData> getHourlyBatteryLevelsPerDay() { return mHourlyBatteryLevelsPerDay; } } No newline at end of file
src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java 0 → 100644 +468 −0 File added.Preview size limit exceeded, changes collapsed. Show changes
tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java 0 → 100644 +454 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.fuelgauge.batteryusage; import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.spy; import android.content.ContentValues; import android.content.Context; import android.text.format.DateUtils; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TimeZone; @RunWith(RobolectricTestRunner.class) public class DataProcessorTest { private static final String FAKE_ENTRY_KEY = "fake_entry_key"; private Context mContext; @Before public void setUp() { MockitoAnnotations.initMocks(this); TimeZone.setDefault(TimeZone.getTimeZone("GMT+8")); mContext = spy(RuntimeEnvironment.application); } @Test public void getBatteryLevelData_emptyHistoryMap_returnNull() { assertThat(DataProcessor.getBatteryLevelData(mContext, null)).isNull(); assertThat(DataProcessor.getBatteryLevelData(mContext, new HashMap<>())).isNull(); } @Test public void getBatteryLevelData_notEnoughData_returnNull() { // The timestamps are within 1 hour. final long[] timestamps = {1000000L, 2000000L, 3000000L}; final int[] levels = {100, 99, 98}; final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = createHistoryMap(timestamps, levels); assertThat(DataProcessor.getBatteryLevelData(mContext, batteryHistoryMap)).isNull(); } @Test public void getBatteryLevelData_returnExpectedResult() { // Timezone GMT+8: 2022-01-01 00:00:00, 2022-01-01 01:00:00, 2022-01-01 02:00:00 final long[] timestamps = {1640966400000L, 1640970000000L, 1640973600000L}; final int[] levels = {100, 99, 98}; final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = createHistoryMap(timestamps, levels); final BatteryLevelData resultData = DataProcessor.getBatteryLevelData(mContext, batteryHistoryMap); final List<Long> expectedDailyTimestamps = List.of(timestamps[0], timestamps[2]); final List<Integer> expectedDailyLevels = List.of(levels[0], levels[2]); final List<List<Long>> expectedHourlyTimestamps = List.of(expectedDailyTimestamps); final List<List<Integer>> expectedHourlyLevels = List.of(expectedDailyLevels); verifyExpectedBatteryLevelData( resultData, expectedDailyTimestamps, expectedDailyLevels, expectedHourlyTimestamps, expectedHourlyLevels); } @Test public void getHistoryMapWithExpectedTimestamps_emptyHistoryMap_returnEmptyMap() { assertThat(DataProcessor .getHistoryMapWithExpectedTimestamps(mContext, new HashMap<>())) .isEmpty(); } @Test public void getHistoryMapWithExpectedTimestamps_returnExpectedMap() { // Timezone GMT+8 final long[] timestamps = { 1640966700000L, // 2022-01-01 00:05:00 1640970180000L, // 2022-01-01 01:03:00 1640973840000L, // 2022-01-01 02:04:00 1640978100000L, // 2022-01-01 03:15:00 1640981400000L // 2022-01-01 04:10:00 }; final int[] levels = {100, 94, 90, 82, 50}; final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = createHistoryMap(timestamps, levels); final Map<Long, Map<String, BatteryHistEntry>> resultMap = DataProcessor.getHistoryMapWithExpectedTimestamps(mContext, batteryHistoryMap); // Timezone GMT+8 final long[] expectedTimestamps = { 1640966400000L, // 2022-01-01 00:00:00 1640970000000L, // 2022-01-01 01:00:00 1640973600000L, // 2022-01-01 02:00:00 1640977200000L, // 2022-01-01 03:00:00 1640980800000L // 2022-01-01 04:00:00 }; final int[] expectedLevels = {100, 94, 90, 84, 56}; assertThat(resultMap).hasSize(expectedLevels.length); for (int index = 0; index < expectedLevels.length; index++) { assertThat(resultMap.get(expectedTimestamps[index]).get(FAKE_ENTRY_KEY).mBatteryLevel) .isEqualTo(expectedLevels[index]); } } @Test public void getLevelDataThroughProcessedHistoryMap_notEnoughData_returnNull() { final long[] timestamps = {100L}; final int[] levels = {100}; final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = createHistoryMap(timestamps, levels); assertThat( DataProcessor.getLevelDataThroughProcessedHistoryMap(mContext, batteryHistoryMap)) .isNull(); } @Test public void getLevelDataThroughProcessedHistoryMap_OneDayData_returnExpectedResult() { // Timezone GMT+8 final long[] timestamps = { 1640966400000L, // 2022-01-01 00:00:00 1640970000000L, // 2022-01-01 01:00:00 1640973600000L, // 2022-01-01 02:00:00 1640977200000L, // 2022-01-01 03:00:00 1640980800000L // 2022-01-01 04:00:00 }; final int[] levels = {100, 94, 90, 82, 50}; final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = createHistoryMap(timestamps, levels); final BatteryLevelData resultData = DataProcessor.getLevelDataThroughProcessedHistoryMap(mContext, batteryHistoryMap); final List<Long> expectedDailyTimestamps = List.of(timestamps[0], timestamps[4]); final List<Integer> expectedDailyLevels = List.of(levels[0], levels[4]); final List<List<Long>> expectedHourlyTimestamps = List.of( List.of(timestamps[0], timestamps[2], timestamps[4]) ); final List<List<Integer>> expectedHourlyLevels = List.of( List.of(levels[0], levels[2], levels[4]) ); verifyExpectedBatteryLevelData( resultData, expectedDailyTimestamps, expectedDailyLevels, expectedHourlyTimestamps, expectedHourlyLevels); } @Test public void getLevelDataThroughProcessedHistoryMap_MultipleDaysData_returnExpectedResult() { // Timezone GMT+8 final long[] timestamps = { 1641038400000L, // 2022-01-01 20:00:00 1641060000000L, // 2022-01-02 02:00:00 1641067200000L, // 2022-01-02 04:00:00 1641081600000L, // 2022-01-02 08:00:00 }; final int[] levels = {100, 94, 90, 82}; final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = createHistoryMap(timestamps, levels); final BatteryLevelData resultData = DataProcessor.getLevelDataThroughProcessedHistoryMap(mContext, batteryHistoryMap); final List<Long> expectedDailyTimestamps = List.of( 1641038400000L, // 2022-01-01 20:00:00 1641052800000L, // 2022-01-02 00:00:00 1641081600000L // 2022-01-02 08:00:00 ); final List<Integer> expectedDailyLevels = new ArrayList<>(); expectedDailyLevels.add(100); expectedDailyLevels.add(null); expectedDailyLevels.add(82); final List<List<Long>> expectedHourlyTimestamps = List.of( List.of( 1641038400000L, // 2022-01-01 20:00:00 1641045600000L, // 2022-01-01 22:00:00 1641052800000L // 2022-01-02 00:00:00 ), List.of( 1641052800000L, // 2022-01-02 00:00:00 1641060000000L, // 2022-01-02 02:00:00 1641067200000L, // 2022-01-02 04:00:00 1641074400000L, // 2022-01-02 06:00:00 1641081600000L // 2022-01-02 08:00:00 ) ); final List<Integer> expectedHourlyLevels1 = new ArrayList<>(); expectedHourlyLevels1.add(100); expectedHourlyLevels1.add(null); expectedHourlyLevels1.add(null); final List<Integer> expectedHourlyLevels2 = new ArrayList<>(); expectedHourlyLevels2.add(null); expectedHourlyLevels2.add(94); expectedHourlyLevels2.add(90); expectedHourlyLevels2.add(null); expectedHourlyLevels2.add(82); final List<List<Integer>> expectedHourlyLevels = List.of( expectedHourlyLevels1, expectedHourlyLevels2 ); verifyExpectedBatteryLevelData( resultData, expectedDailyTimestamps, expectedDailyLevels, expectedHourlyTimestamps, expectedHourlyLevels); } @Test public void getTimestampSlots_emptyRawList_returnEmptyList() { final List<Long> resultList = DataProcessor.getTimestampSlots(new ArrayList<>()); assertThat(resultList).isEmpty(); } @Test public void getTimestampSlots_startWithEvenHour_returnExpectedResult() { final Calendar startCalendar = Calendar.getInstance(); startCalendar.set(2022, 6, 5, 6, 30, 50); // 2022-07-05 06:30:50 final Calendar endCalendar = Calendar.getInstance(); endCalendar.set(2022, 6, 5, 22, 30, 50); // 2022-07-05 22:30:50 final Calendar expectedStartCalendar = Calendar.getInstance(); expectedStartCalendar.set(2022, 6, 5, 6, 0, 0); // 2022-07-05 06:00:00 final Calendar expectedEndCalendar = Calendar.getInstance(); expectedEndCalendar.set(2022, 6, 5, 22, 0, 0); // 2022-07-05 22:00:00 verifyExpectedTimestampSlots( startCalendar, endCalendar, expectedStartCalendar, expectedEndCalendar); } @Test public void getTimestampSlots_startWithOddHour_returnExpectedResult() { final Calendar startCalendar = Calendar.getInstance(); startCalendar.set(2022, 6, 5, 5, 0, 50); // 2022-07-05 05:00:50 final Calendar endCalendar = Calendar.getInstance(); endCalendar.set(2022, 6, 6, 21, 00, 50); // 2022-07-06 21:00:50 final Calendar expectedStartCalendar = Calendar.getInstance(); expectedStartCalendar.set(2022, 6, 5, 6, 00, 00); // 2022-07-05 06:00:00 final Calendar expectedEndCalendar = Calendar.getInstance(); expectedEndCalendar.set(2022, 6, 6, 20, 00, 00); // 2022-07-06 20:00:00 verifyExpectedTimestampSlots( startCalendar, endCalendar, expectedStartCalendar, expectedEndCalendar); } @Test public void getDailyTimestamps_notEnoughData_returnEmptyList() { assertThat(DataProcessor.getDailyTimestamps(new ArrayList<>())).isEmpty(); assertThat(DataProcessor.getDailyTimestamps(List.of(100L))).isEmpty(); } @Test public void getDailyTimestamps_OneDayData_returnExpectedList() { // Timezone GMT+8 final List<Long> timestamps = List.of( 1640966400000L, // 2022-01-01 00:00:00 1640970000000L, // 2022-01-01 01:00:00 1640973600000L, // 2022-01-01 02:00:00 1640977200000L, // 2022-01-01 03:00:00 1640980800000L // 2022-01-01 04:00:00 ); final List<Long> expectedTimestamps = List.of( 1640966400000L, // 2022-01-01 00:00:00 1640980800000L // 2022-01-01 04:00:00 ); assertThat(DataProcessor.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps); } @Test public void getDailyTimestamps_MultipleDaysData_returnExpectedList() { // Timezone GMT+8 final List<Long> timestamps = List.of( 1640988000000L, // 2022-01-01 06:00:00 1641060000000L, // 2022-01-02 02:00:00 1641160800000L, // 2022-01-03 06:00:00 1641254400000L // 2022-01-04 08:00:00 ); final List<Long> expectedTimestamps = List.of( 1640988000000L, // 2022-01-01 06:00:00 1641052800000L, // 2022-01-02 00:00:00 1641139200000L, // 2022-01-03 00:00:00 1641225600000L, // 2022-01-04 00:00:00 1641254400000L // 2022-01-04 08:00:00 ); assertThat(DataProcessor.getDailyTimestamps(timestamps)).isEqualTo(expectedTimestamps); } @Test public void isFromFullCharge_emptyData_returnFalse() { assertThat(DataProcessor.isFromFullCharge(null)).isFalse(); assertThat(DataProcessor.isFromFullCharge(new HashMap<>())).isFalse(); } @Test public void isFromFullCharge_notChargedData_returnFalse() { final Map<String, BatteryHistEntry> entryMap = new HashMap<>(); final ContentValues values = new ContentValues(); values.put("batteryLevel", 98); final BatteryHistEntry entry = new BatteryHistEntry(values); entryMap.put(FAKE_ENTRY_KEY, entry); assertThat(DataProcessor.isFromFullCharge(entryMap)).isFalse(); } @Test public void isFromFullCharge_chargedData_returnTrue() { final Map<String, BatteryHistEntry> entryMap = new HashMap<>(); final ContentValues values = new ContentValues(); values.put("batteryLevel", 100); final BatteryHistEntry entry = new BatteryHistEntry(values); entryMap.put(FAKE_ENTRY_KEY, entry); assertThat(DataProcessor.isFromFullCharge(entryMap)).isTrue(); } @Test public void findNearestTimestamp_returnExpectedResult() { long[] results = DataProcessor.findNearestTimestamp( Arrays.asList(10L, 20L, 30L, 40L), /*target=*/ 15L); assertThat(results).isEqualTo(new long[] {10L, 20L}); results = DataProcessor.findNearestTimestamp( Arrays.asList(10L, 20L, 30L, 40L), /*target=*/ 10L); assertThat(results).isEqualTo(new long[] {10L, 10L}); results = DataProcessor.findNearestTimestamp( Arrays.asList(10L, 20L, 30L, 40L), /*target=*/ 5L); assertThat(results).isEqualTo(new long[] {0L, 10L}); results = DataProcessor.findNearestTimestamp( Arrays.asList(10L, 20L, 30L, 40L), /*target=*/ 50L); assertThat(results).isEqualTo(new long[] {40L, 0L}); } @Test public void getTimestampOfNextDay_returnExpectedResult() { // 2021-02-28 06:00:00 => 2021-03-01 00:00:00 assertThat(DataProcessor.getTimestampOfNextDay(1614463200000L)) .isEqualTo(1614528000000L); // 2021-12-31 16:00:00 => 2022-01-01 00:00:00 assertThat(DataProcessor.getTimestampOfNextDay(1640937600000L)) .isEqualTo(1640966400000L); } private static Map<Long, Map<String, BatteryHistEntry>> createHistoryMap( final long[] timestamps, final int[] levels) { final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap = new HashMap<>(); for (int index = 0; index < timestamps.length; index++) { final Map<String, BatteryHistEntry> entryMap = new HashMap<>(); final ContentValues values = new ContentValues(); values.put(BatteryHistEntry.KEY_BATTERY_LEVEL, levels[index]); final BatteryHistEntry entry = new BatteryHistEntry(values); entryMap.put(FAKE_ENTRY_KEY, entry); batteryHistoryMap.put(timestamps[index], entryMap); } return batteryHistoryMap; } private static void verifyExpectedBatteryLevelData( final BatteryLevelData resultData, final List<Long> expectedDailyTimestamps, final List<Integer> expectedDailyLevels, final List<List<Long>> expectedHourlyTimestamps, final List<List<Integer>> expectedHourlyLevels) { final BatteryLevelData.PeriodBatteryLevelData dailyResultData = resultData.getDailyBatteryLevels(); final List<BatteryLevelData.PeriodBatteryLevelData> hourlyResultData = resultData.getHourlyBatteryLevelsPerDay(); verifyExpectedDailyBatteryLevelData( dailyResultData, expectedDailyTimestamps, expectedDailyLevels); verifyExpectedHourlyBatteryLevelData( hourlyResultData, expectedHourlyTimestamps, expectedHourlyLevels); } private static void verifyExpectedDailyBatteryLevelData( final BatteryLevelData.PeriodBatteryLevelData dailyResultData, final List<Long> expectedDailyTimestamps, final List<Integer> expectedDailyLevels) { assertThat(dailyResultData.getTimestamps()).isEqualTo(expectedDailyTimestamps); assertThat(dailyResultData.getLevels()).isEqualTo(expectedDailyLevels); } private static void verifyExpectedHourlyBatteryLevelData( final List<BatteryLevelData.PeriodBatteryLevelData> hourlyResultData, final List<List<Long>> expectedHourlyTimestamps, final List<List<Integer>> expectedHourlyLevels) { final int expectedHourlySize = expectedHourlyTimestamps.size(); assertThat(hourlyResultData).hasSize(expectedHourlySize); for (int dailyIndex = 0; dailyIndex < expectedHourlySize; dailyIndex++) { assertThat(hourlyResultData.get(dailyIndex).getTimestamps()) .isEqualTo(expectedHourlyTimestamps.get(dailyIndex)); assertThat(hourlyResultData.get(dailyIndex).getLevels()) .isEqualTo(expectedHourlyLevels.get(dailyIndex)); } } private static void verifyExpectedTimestampSlots( final Calendar start, final Calendar end, final Calendar expectedStart, final Calendar expectedEnd) { expectedStart.set(Calendar.MILLISECOND, 0); expectedEnd.set(Calendar.MILLISECOND, 0); final ArrayList<Long> timestampSlots = new ArrayList<>(); timestampSlots.add(start.getTimeInMillis()); timestampSlots.add(end.getTimeInMillis()); final List<Long> resultList = DataProcessor.getTimestampSlots(timestampSlots); for (int index = 0; index < resultList.size(); index++) { final long expectedTimestamp = expectedStart.getTimeInMillis() + index * DateUtils.HOUR_IN_MILLIS; assertThat(resultList.get(index)).isEqualTo(expectedTimestamp); } assertThat(resultList.get(resultList.size() - 1)) .isEqualTo(expectedEnd.getTimeInMillis()); } }