From a1a7cba6a6c1961925a7f1e1fa7c5a3cd39c25ed Mon Sep 17 00:00:00 2001 From: Kuan Wang Date: Tue, 20 Dec 2022 12:48:42 +0800 Subject: [PATCH] Move the getBatteryLevelData function from DataProcessor to DataProcessManager and start the async task in DataProcessManager when there is no battery level data. Test: make RunSettingsRoboTests + manually Bug: 260964903 Change-Id: Ie36ab6d121a5596a3abc16e7f570dd0d9b32e11c --- .../BatteryChartPreferenceController.java | 2 +- .../batteryusage/DataProcessManager.java | 136 ++++++++++++++-- .../fuelgauge/batteryusage/DataProcessor.java | 153 ++++-------------- .../BatteryChartPreferenceControllerTest.java | 4 +- .../batteryusage/DataProcessManagerTest.java | 150 +++++++++++++++++ .../batteryusage/DataProcessorTest.java | 60 ------- 6 files changed, 310 insertions(+), 195 deletions(-) diff --git a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java index c2eab5749cd..b048dbc083b 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java +++ b/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceController.java @@ -209,7 +209,7 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll // Ensure the battery chart group is visible for users. animateBatteryChartViewGroup(); final BatteryLevelData batteryLevelData = - DataProcessor.getBatteryLevelData(mContext, mHandler, batteryHistoryMap, + DataProcessManager.getBatteryLevelData(mContext, mHandler, batteryHistoryMap, batteryUsageMap -> { mBatteryUsageMap = batteryUsageMap; refreshUi(); diff --git a/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java b/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java index 784f64fab1e..dd6b9d9d4af 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java +++ b/src/com/android/settings/fuelgauge/batteryusage/DataProcessManager.java @@ -20,11 +20,13 @@ import android.app.usage.UsageEvents; import android.content.Context; import android.os.AsyncTask; import android.os.Handler; +import android.os.Looper; import android.os.UserHandle; import android.os.UserManager; import android.util.Log; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.settings.Utils; @@ -39,7 +41,7 @@ import java.util.Map; /** * Manages the async tasks to process battery and app usage data. * - * For now, there exist 3 async tasks in this manager: + * For now, there exist 4 async tasks in this manager: * * - * The 3 async tasks will be started at the same time. + * If there is battery level data, the first 3 async tasks will be started at the same time. * + * + * If there is no battery level data, the 4th async task will be started only and the usage map + * callback function will be applied directly to show the app list on the UI. */ public class DataProcessManager { private static final String TAG = "DataProcessManager"; @@ -74,23 +81,25 @@ public class DataProcessManager { // The start timestamp of battery level data. As we don't know when is the full charge cycle // start time when loading app usage data, this value is used as the start time of querying app // usage data. - private long mStartTimestampOfLevelData = 0; + private long mStartTimestampOfLevelData; private boolean mIsCurrentBatteryHistoryLoaded = false; private boolean mIsCurrentAppUsageLoaded = false; private boolean mIsDatabaseAppUsageLoaded = false; // Used to identify whether screen-on time data should be shown in the UI. private boolean mShowScreenOnTime = true; + // Used to identify whether battery level data should be shown in the UI. + private boolean mShowBatteryLevel = true; private List mAppUsageEventList = new ArrayList<>(); /** - * Constructor when this exists battery level data. + * Constructor when there exists battery level data. */ DataProcessManager( Context context, Handler handler, - final DataProcessor.UsageMapAsyncResponse callbackFunction, + @NonNull final DataProcessor.UsageMapAsyncResponse callbackFunction, @NonNull final List hourlyBatteryLevelsPerDay, @NonNull final Map> batteryHistoryMap) { mContext = context.getApplicationContext(); @@ -102,16 +111,40 @@ public class DataProcessManager { mStartTimestampOfLevelData = getStartTimestampOfBatteryLevelData(); } + /** + * Constructor when there is no battery level data. + */ + DataProcessManager( + Context context, + Handler handler, + @NonNull final DataProcessor.UsageMapAsyncResponse callbackFunction) { + mContext = context.getApplicationContext(); + mHandler = handler; + mUserManager = mContext.getSystemService(UserManager.class); + mCallbackFunction = callbackFunction; + // When there is no battery level data, don't show screen-on time and battery level chart on + // the UI. + mShowScreenOnTime = false; + mShowBatteryLevel = false; + } + /** * Starts the async tasks to load battery history data and app usage data. */ public void start() { - // Load the latest battery history data from the service. - loadCurrentBatteryHistoryMap(); - // Load app usage list from database. - loadDatabaseAppUsageList(); - // Load the latest app usage list from the service. - loadCurrentAppUsageList(); + // If we have battery level data, load the battery history map and app usage simultaneously. + if (mShowBatteryLevel) { + // Loads the latest battery history data from the service. + loadCurrentBatteryHistoryMap(); + // Loads app usage list from database. + loadDatabaseAppUsageList(); + // Loads the latest app usage list from the service. + loadCurrentAppUsageList(); + } else { + // If there is no battery level data, only load the battery history data from service + // and show it as the app list directly. + loadAndApplyBatteryMapFromServiceOnly(); + } } @VisibleForTesting @@ -154,6 +187,11 @@ public class DataProcessManager { return mShowScreenOnTime; } + @VisibleForTesting + boolean getShowBatteryLevel() { + return mShowBatteryLevel; + } + private void loadCurrentBatteryHistoryMap() { new AsyncTask>() { @Override @@ -279,6 +317,35 @@ public class DataProcessManager { }.execute(); } + private void loadAndApplyBatteryMapFromServiceOnly() { + new AsyncTask>>() { + @Override + protected Map> doInBackground(Void... voids) { + final long startTime = System.currentTimeMillis(); + final Map> batteryUsageMap = + DataProcessor.getBatteryUsageMapFromStatsService(mContext); + DataProcessor.loadLabelAndIcon(batteryUsageMap); + Log.d(TAG, String.format( + "execute loadAndApplyBatteryMapFromServiceOnly size=%d in %d/ms", + batteryUsageMap.size(), (System.currentTimeMillis() - startTime))); + return batteryUsageMap; + } + + @Override + protected void onPostExecute( + final Map> batteryUsageMap) { + // Set the unused variables to null. + mContext = null; + // Post results back to main thread to refresh UI. + if (mHandler != null && mCallbackFunction != null) { + mHandler.post(() -> { + mCallbackFunction.onBatteryUsageMapLoaded(batteryUsageMap); + }); + } + } + }.execute(); + } + private void tryToProcessAppUsageData() { // Only when all app usage events has been loaded, start processing app usage data to an // intermediate result for further use. @@ -313,6 +380,10 @@ public class DataProcessManager { private void generateFinalDataAndApplyCallback() { // TODO: generate the final data including battery usage map and device screen-on time and // then apply the callback function. + // Set the unused variables to null. + mContext = null; + mHourlyBatteryLevelsPerDay = null; + mBatteryHistoryMap = null; } // Whether we should load app usage data from service or database. @@ -350,4 +421,47 @@ public class DataProcessManager { Utils.getManagedProfile(mUserManager); return userHandle != null ? userHandle.getIdentifier() : Integer.MIN_VALUE; } + + /** + * @return Returns battery level data and start async task to compute battery diff usage data + * and load app labels + icons. + * Returns null if the input is invalid or not having at least 2 hours data. + */ + @Nullable + public static BatteryLevelData getBatteryLevelData( + Context context, + @Nullable Handler handler, + @Nullable final Map> batteryHistoryMap, + final DataProcessor.UsageMapAsyncResponse asyncResponseDelegate) { + if (batteryHistoryMap == null || batteryHistoryMap.isEmpty()) { + Log.d(TAG, "batteryHistoryMap is null in getBatteryLevelData()"); + new DataProcessManager(context, handler, asyncResponseDelegate).start(); + return null; + } + handler = handler != null ? handler : new Handler(Looper.getMainLooper()); + // Process raw history map data into hourly timestamps. + final Map> processedBatteryHistoryMap = + DataProcessor.getHistoryMapWithExpectedTimestamps(context, batteryHistoryMap); + // Wrap and processed history map into easy-to-use format for UI rendering. + final BatteryLevelData batteryLevelData = + DataProcessor.getLevelDataThroughProcessedHistoryMap( + context, processedBatteryHistoryMap); + if (batteryLevelData == null) { + new DataProcessManager(context, handler, asyncResponseDelegate).start(); + Log.d(TAG, "getBatteryLevelData() returns null"); + return null; + } + + // TODO: replace the task below with new DataProcessManager(...).start() after + // DataProcessManager is completed; + // Start the async task to compute diff usage data and load labels and icons. + new DataProcessor.ComputeUsageMapAndLoadItemsTask( + context, + handler, + asyncResponseDelegate, + batteryLevelData.getHourlyBatteryLevelsPerDay(), + processedBatteryHistoryMap).execute(); + + return batteryLevelData; + } } diff --git a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java index bfedeab9264..3b3a13582da 100644 --- a/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java +++ b/src/com/android/settings/fuelgauge/batteryusage/DataProcessor.java @@ -32,7 +32,6 @@ import android.os.BatteryStatsManager; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; import android.os.Handler; -import android.os.Looper; import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; @@ -113,48 +112,6 @@ public final class DataProcessor { private DataProcessor() { } - /** - * @return Returns battery level data and start async task to compute battery diff usage data - * and load app labels + icons. - * Returns null if the input is invalid or not having at least 2 hours data. - */ - @Nullable - public static BatteryLevelData getBatteryLevelData( - Context context, - @Nullable Handler handler, - @Nullable final Map> batteryHistoryMap, - final UsageMapAsyncResponse asyncResponseDelegate) { - if (batteryHistoryMap == null || batteryHistoryMap.isEmpty()) { - Log.d(TAG, "batteryHistoryMap is null in getBatteryLevelData()"); - loadBatteryUsageDataFromBatteryStatsService( - context, handler, asyncResponseDelegate); - return null; - } - handler = handler != null ? handler : new Handler(Looper.getMainLooper()); - // Process raw history map data into hourly timestamps. - final Map> processedBatteryHistoryMap = - getHistoryMapWithExpectedTimestamps(context, batteryHistoryMap); - // Wrap and processed history map into easy-to-use format for UI rendering. - final BatteryLevelData batteryLevelData = - getLevelDataThroughProcessedHistoryMap(context, processedBatteryHistoryMap); - if (batteryLevelData == null) { - loadBatteryUsageDataFromBatteryStatsService( - context, handler, asyncResponseDelegate); - Log.d(TAG, "getBatteryLevelData() returns null"); - return null; - } - - // Start the async task to compute diff usage data and load labels and icons. - new ComputeUsageMapAndLoadItemsTask( - context, - handler, - asyncResponseDelegate, - batteryLevelData.getHourlyBatteryLevelsPerDay(), - processedBatteryHistoryMap).execute(); - - return batteryLevelData; - } - /** * @return Returns battery usage data of different entries. * Returns null if the input is invalid or there is no enough data. @@ -361,7 +318,6 @@ public final class DataProcessor { * The keys of processed history map should contain every hour between the start and end * timestamp. If there's no data in some key, the value will be the empty hashmap. */ - @VisibleForTesting static Map> getHistoryMapWithExpectedTimestamps( Context context, final Map> batteryHistoryMap) { @@ -385,7 +341,6 @@ public final class DataProcessor { return resultMap; } - @VisibleForTesting @Nullable static BatteryLevelData getLevelDataThroughProcessedHistoryMap( Context context, @@ -624,16 +579,37 @@ public final class DataProcessor { } /** - * Starts the async task to load battery diff usage data and load app labels + icons. + * @return Returns the overall battery usage data from battery stats service directly. + * + * The returned value should be always a 2d map and composed by only 1 part: + * - [SELECTED_INDEX_ALL][SELECTED_INDEX_ALL] */ - private static void loadBatteryUsageDataFromBatteryStatsService( - Context context, - @Nullable Handler handler, - final UsageMapAsyncResponse asyncResponseDelegate) { - new LoadUsageMapFromBatteryStatsServiceTask( - context, - handler, - asyncResponseDelegate).execute(); + static Map> getBatteryUsageMapFromStatsService( + final Context context) { + final Map> resultMap = new HashMap<>(); + final Map allUsageMap = new HashMap<>(); + // Always construct the map whether the value is null or not. + allUsageMap.put(SELECTED_INDEX_ALL, + generateBatteryDiffData(context, getBatteryHistListFromFromStatsService(context))); + resultMap.put(SELECTED_INDEX_ALL, allUsageMap); + processBatteryDiffData(context, resultMap); + return resultMap; + } + + static void loadLabelAndIcon( + @Nullable final Map> batteryUsageMap) { + if (batteryUsageMap == null) { + return; + } + // Pre-loads each BatteryDiffEntry relative icon and label for all slots. + final BatteryDiffData batteryUsageMapForAll = + batteryUsageMap.get(SELECTED_INDEX_ALL).get(SELECTED_INDEX_ALL); + if (batteryUsageMapForAll != null) { + batteryUsageMapForAll.getAppDiffEntryList().forEach( + entry -> entry.loadLabelAndIcon()); + batteryUsageMapForAll.getSystemDiffEntryList().forEach( + entry -> entry.loadLabelAndIcon()); + } } @Nullable @@ -672,24 +648,6 @@ public final class DataProcessor { return events; } - /** - * @return Returns the overall battery usage data from battery stats service directly. - * - * The returned value should be always a 2d map and composed by only 1 part: - * - [SELECTED_INDEX_ALL][SELECTED_INDEX_ALL] - */ - private static Map> getBatteryUsageMapFromStatsService( - final Context context) { - final Map> resultMap = new HashMap<>(); - final Map allUsageMap = new HashMap<>(); - // Always construct the map whether the value is null or not. - allUsageMap.put(SELECTED_INDEX_ALL, - generateBatteryDiffData(context, getBatteryHistListFromFromStatsService(context))); - resultMap.put(SELECTED_INDEX_ALL, allUsageMap); - processBatteryDiffData(context, resultMap); - return resultMap; - } - @Nullable private static List getBatteryHistListFromFromStatsService( final Context context) { @@ -1469,22 +1427,6 @@ public final class DataProcessor { return true; } - private static void loadLabelAndIcon( - @Nullable final Map> batteryUsageMap) { - if (batteryUsageMap == null) { - return; - } - // Pre-loads each BatteryDiffEntry relative icon and label for all slots. - final BatteryDiffData batteryUsageMapForAll = - batteryUsageMap.get(SELECTED_INDEX_ALL).get(SELECTED_INDEX_ALL); - if (batteryUsageMapForAll != null) { - batteryUsageMapForAll.getAppDiffEntryList().forEach( - entry -> entry.loadLabelAndIcon()); - batteryUsageMapForAll.getSystemDiffEntryList().forEach( - entry -> entry.loadLabelAndIcon()); - } - } - private static long getTimestampWithDayDiff(final long timestamp, final int dayDiff) { final Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(timestamp); @@ -1527,7 +1469,7 @@ public final class DataProcessor { } // Compute diff map and loads all items (icon and label) in the background. - private static class ComputeUsageMapAndLoadItemsTask + static class ComputeUsageMapAndLoadItemsTask extends AsyncTask>> { Context mApplicationContext; @@ -1536,7 +1478,7 @@ public final class DataProcessor { private List mHourlyBatteryLevelsPerDay; private Map> mBatteryHistoryMap; - private ComputeUsageMapAndLoadItemsTask( + ComputeUsageMapAndLoadItemsTask( Context context, Handler handler, final UsageMapAsyncResponse asyncResponseDelegate, @@ -1594,35 +1536,4 @@ public final class DataProcessor { } } } - - // Loads battery usage data from battery stats service directly and loads all items (icon and - // label) in the background. - private static final class LoadUsageMapFromBatteryStatsServiceTask - extends ComputeUsageMapAndLoadItemsTask { - - private LoadUsageMapFromBatteryStatsServiceTask( - Context context, - Handler handler, - final UsageMapAsyncResponse asyncResponseDelegate) { - super(context, handler, asyncResponseDelegate, /*hourlyBatteryLevelsPerDay=*/ null, - /*batteryHistoryMap=*/ null); - } - - @Override - protected Map> doInBackground(Void... voids) { - if (mApplicationContext == null - || mHandler == null - || mAsyncResponseDelegate == null) { - Log.e(TAG, "invalid input for ComputeUsageMapAndLoadItemsTask()"); - return null; - } - final long startTime = System.currentTimeMillis(); - final Map> batteryUsageMap = - getBatteryUsageMapFromStatsService(mApplicationContext); - loadLabelAndIcon(batteryUsageMap); - Log.d(TAG, String.format("execute LoadUsageMapFromBatteryStatsServiceTask in %d/ms", - (System.currentTimeMillis() - startTime))); - return batteryUsageMap; - } - } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java index 57963aaa111..839cae255db 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BatteryChartPreferenceControllerTest.java @@ -371,8 +371,8 @@ public final class BatteryChartPreferenceControllerTest { @Test public void getTotalHours_getExpectedResult() { Map> batteryHistoryMap = createBatteryHistoryMap(60); - BatteryLevelData batteryLevelData = DataProcessor.getBatteryLevelData(mContext, null, - batteryHistoryMap, null); + BatteryLevelData batteryLevelData = + DataProcessManager.getBatteryLevelData(mContext, null, batteryHistoryMap, null); final int totalHour = BatteryChartPreferenceController.getTotalHours(batteryLevelData); diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java index 1bfff0749bb..ee374692cae 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessManagerTest.java @@ -21,16 +21,21 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import android.app.usage.IUsageStatsManager; import android.app.usage.UsageEvents; +import android.content.ContentValues; import android.content.Context; +import android.content.Intent; import android.database.MatrixCursor; +import android.os.BatteryManager; import android.os.Parcel; import android.os.RemoteException; import android.os.UserManager; +import android.text.format.DateUtils; import com.android.settings.fuelgauge.batteryusage.db.AppUsageEventEntity; @@ -43,10 +48,14 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; @RunWith(RobolectricTestRunner.class) public final class DataProcessManagerTest { + private static final String FAKE_ENTRY_KEY = "fake_entry_key"; + private Context mContext; private DataProcessManager mDataProcessManager; @@ -54,6 +63,8 @@ public final class DataProcessManagerTest { private IUsageStatsManager mUsageStatsManager; @Mock private UserManager mUserManager; + @Mock + private Intent mIntent; @Before public void setUp() { @@ -65,12 +76,23 @@ public final class DataProcessManagerTest { doReturn(mUserManager) .when(mContext) .getSystemService(UserManager.class); + 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()); mDataProcessManager = new DataProcessManager( mContext, /*handler=*/ null, /*callbackFunction=*/ null, /*hourlyBatteryLevelsPerDay=*/ new ArrayList<>(), /*batteryHistoryMap=*/ null); } + @Test + public void constructor_noLevelData() { + final DataProcessManager dataProcessManager = + new DataProcessManager(mContext, /*handler=*/ null, /*callbackFunction=*/ null); + assertThat(dataProcessManager.getShowScreenOnTime()).isFalse(); + assertThat(dataProcessManager.getShowBatteryLevel()).isFalse(); + } + @Test public void start_loadEmptyDatabaseAppUsageData() { final MatrixCursor cursor = new MatrixCursor( @@ -204,6 +226,66 @@ public final class DataProcessManagerTest { assertThat(dataProcessManager.getStartTimestampOfBatteryLevelData()).isEqualTo(0); } + @Test + public void getBatteryLevelData_emptyHistoryMap_returnNull() { + assertThat(DataProcessManager.getBatteryLevelData( + mContext, + /*handler=*/ null, + /*batteryHistoryMap=*/ null, + /*asyncResponseDelegate=*/ null)) + .isNull(); + assertThat(DataProcessManager.getBatteryLevelData( + mContext, /*handler=*/ null, new HashMap<>(), /*asyncResponseDelegate=*/ null)) + .isNull(); + } + + @Test + public void getBatteryLevelData_notEnoughData_returnNull() { + // The timestamps and the current time are within half hour before an even hour. + final long[] timestamps = { + DateUtils.HOUR_IN_MILLIS * 2 - 300L, + DateUtils.HOUR_IN_MILLIS * 2 - 200L, + DateUtils.HOUR_IN_MILLIS * 2 - 100L}; + final int[] levels = {100, 99, 98}; + final Map> batteryHistoryMap = + createHistoryMap(timestamps, levels); + DataProcessor.sFakeCurrentTimeMillis = timestamps[timestamps.length - 1]; + + assertThat(DataProcessManager.getBatteryLevelData( + mContext, /*handler=*/ null, batteryHistoryMap, /*asyncResponseDelegate=*/ null)) + .isNull(); + } + + @Test + public void getBatteryLevelData_returnExpectedResult() { + // Timezone GMT+8: 2022-01-01 00:00:00, 2022-01-01 01:00:00 + final long[] timestamps = {1640966400000L, 1640970000000L}; + final int[] levels = {100, 99}; + final Map> batteryHistoryMap = + createHistoryMap(timestamps, levels); + DataProcessor.sFakeCurrentTimeMillis = timestamps[timestamps.length - 1]; + + final BatteryLevelData resultData = + DataProcessManager.getBatteryLevelData( + mContext, + /*handler=*/ null, + batteryHistoryMap, + /*asyncResponseDelegate=*/ null); + + final List expectedDailyTimestamps = List.of( + 1640966400000L, // 2022-01-01 00:00:00 + 1640973600000L); // 2022-01-01 02:00:00 + final List expectedDailyLevels = List.of(100, 66); + final List> expectedHourlyTimestamps = List.of(expectedDailyTimestamps); + final List> expectedHourlyLevels = List.of(expectedDailyLevels); + verifyExpectedBatteryLevelData( + resultData, + expectedDailyTimestamps, + expectedDailyLevels, + expectedHourlyTimestamps, + expectedHourlyLevels); + } + private UsageEvents getUsageEvents(final List events) { UsageEvents usageEvents = new UsageEvents(events, new String[] {"package"}); Parcel parcel = Parcel.obtain(); @@ -222,9 +304,77 @@ public final class DataProcessManagerTest { return event; } + private static Map> createHistoryMap( + final long[] timestamps, final int[] levels) { + final Map> batteryHistoryMap = new HashMap<>(); + for (int index = 0; index < timestamps.length; index++) { + final Map entryMap = new HashMap<>(); + final ContentValues values = getContentValuesWithBatteryLevel(levels[index]); + final BatteryHistEntry entry = new BatteryHistEntry(values); + entryMap.put(FAKE_ENTRY_KEY, entry); + batteryHistoryMap.put(timestamps[index], entryMap); + } + return batteryHistoryMap; + } + + private static ContentValues getContentValuesWithBatteryLevel(final int level) { + final ContentValues values = new ContentValues(); + final DeviceBatteryState deviceBatteryState = + DeviceBatteryState + .newBuilder() + .setBatteryLevel(level) + .build(); + final BatteryInformation batteryInformation = + BatteryInformation + .newBuilder() + .setDeviceBatteryState(deviceBatteryState) + .build(); + values.put(BatteryHistEntry.KEY_BATTERY_INFORMATION, + ConvertUtils.convertBatteryInformationToString(batteryInformation)); + return values; + } + private void assertAppUsageEvent( final AppUsageEvent event, final AppUsageEventType eventType, final long timestamp) { assertThat(event.getType()).isEqualTo(eventType); assertThat(event.getTimestamp()).isEqualTo(timestamp); } + + private static void verifyExpectedBatteryLevelData( + final BatteryLevelData resultData, + final List expectedDailyTimestamps, + final List expectedDailyLevels, + final List> expectedHourlyTimestamps, + final List> expectedHourlyLevels) { + final BatteryLevelData.PeriodBatteryLevelData dailyResultData = + resultData.getDailyBatteryLevels(); + final List hourlyResultData = + resultData.getHourlyBatteryLevelsPerDay(); + verifyExpectedDailyBatteryLevelData( + dailyResultData, expectedDailyTimestamps, expectedDailyLevels); + verifyExpectedHourlyBatteryLevelData( + hourlyResultData, expectedHourlyTimestamps, expectedHourlyLevels); + } + + private static void verifyExpectedDailyBatteryLevelData( + final BatteryLevelData.PeriodBatteryLevelData dailyResultData, + final List expectedDailyTimestamps, + final List expectedDailyLevels) { + assertThat(dailyResultData.getTimestamps()).isEqualTo(expectedDailyTimestamps); + assertThat(dailyResultData.getLevels()).isEqualTo(expectedDailyLevels); + } + + private static void verifyExpectedHourlyBatteryLevelData( + final List hourlyResultData, + final List> expectedHourlyTimestamps, + final List> 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)); + } + } } diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java index 0f957541e44..aab3cb3a2f3 100644 --- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java +++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/DataProcessorTest.java @@ -98,66 +98,6 @@ public final class DataProcessorTest { doReturn(mUserManager).when(mContext).getSystemService(UserManager.class); } - @Test - public void getBatteryLevelData_emptyHistoryMap_returnNull() { - assertThat(DataProcessor.getBatteryLevelData( - mContext, - /*handler=*/ null, - /*batteryHistoryMap=*/ null, - /*asyncResponseDelegate=*/ null)) - .isNull(); - assertThat(DataProcessor.getBatteryLevelData( - mContext, /*handler=*/ null, new HashMap<>(), /*asyncResponseDelegate=*/ null)) - .isNull(); - } - - @Test - public void getBatteryLevelData_notEnoughData_returnNull() { - // The timestamps and the current time are within half hour before an even hour. - final long[] timestamps = { - DateUtils.HOUR_IN_MILLIS * 2 - 300L, - DateUtils.HOUR_IN_MILLIS * 2 - 200L, - DateUtils.HOUR_IN_MILLIS * 2 - 100L}; - final int[] levels = {100, 99, 98}; - final Map> batteryHistoryMap = - createHistoryMap(timestamps, levels); - DataProcessor.sFakeCurrentTimeMillis = timestamps[timestamps.length - 1]; - - assertThat(DataProcessor.getBatteryLevelData( - mContext, /*handler=*/ null, batteryHistoryMap, /*asyncResponseDelegate=*/ null)) - .isNull(); - } - - @Test - public void getBatteryLevelData_returnExpectedResult() { - // Timezone GMT+8: 2022-01-01 00:00:00, 2022-01-01 01:00:00 - final long[] timestamps = {1640966400000L, 1640970000000L}; - final int[] levels = {100, 99}; - final Map> batteryHistoryMap = - createHistoryMap(timestamps, levels); - DataProcessor.sFakeCurrentTimeMillis = timestamps[timestamps.length - 1]; - - final BatteryLevelData resultData = - DataProcessor.getBatteryLevelData( - mContext, - /*handler=*/ null, - batteryHistoryMap, - /*asyncResponseDelegate=*/ null); - - final List expectedDailyTimestamps = List.of( - 1640966400000L, // 2022-01-01 00:00:00 - 1640973600000L); // 2022-01-01 02:00:00 - final List expectedDailyLevels = List.of(100, 66); - final List> expectedHourlyTimestamps = List.of(expectedDailyTimestamps); - final List> expectedHourlyLevels = List.of(expectedDailyLevels); - verifyExpectedBatteryLevelData( - resultData, - expectedDailyTimestamps, - expectedDailyLevels, - expectedHourlyTimestamps, - expectedHourlyLevels); - } - @Test public void getAppUsageEvents_returnExpectedResult() throws RemoteException { UserInfo userInfo = new UserInfo(/*id=*/ 0, "user_0", /*flags=*/ 0);