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
This commit is contained in:
Kuan Wang
2022-12-20 12:48:42 +08:00
parent 0b2a23dedc
commit a1a7cba6a6
6 changed files with 310 additions and 195 deletions

View File

@@ -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();

View File

@@ -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:
* <ul>
* <li>loadCurrentBatteryHistoryMap: load the latest battery history data from battery stats
* service.</li>
@@ -47,9 +49,11 @@ import java.util.Map;
* from usage stats service.</li>
* <li>loadDatabaseAppUsageList: load the necessary app usage data (after last full charge) from
* database</li>
* <li>loadAndApplyBatteryMapFromServiceOnly: load all the battery history data (should be after
* last full charge) from battery stats service and apply the callback function directly</li>
* </ul>
*
* 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.
* <ul>
* <li>After loadCurrentAppUsageList and loadDatabaseAppUsageList complete, which means all app
* usage data has been loaded, the intermediate usage result will be generated.</li>
@@ -59,6 +63,9 @@ import java.util.Map;
* <li>If current user is locked, which means we couldn't get the latest app usage data,
* screen-on time will not be shown in the UI and empty screen-on time data will be returned.</li>
* </ul>
*
* 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<AppUsageEvent> 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<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay,
@NonNull final Map<Long, Map<String, BatteryHistEntry>> 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<Void, Void, Map<String, BatteryHistEntry>>() {
@Override
@@ -279,6 +317,35 @@ public class DataProcessManager {
}.execute();
}
private void loadAndApplyBatteryMapFromServiceOnly() {
new AsyncTask<Void, Void, Map<Integer, Map<Integer, BatteryDiffData>>>() {
@Override
protected Map<Integer, Map<Integer, BatteryDiffData>> doInBackground(Void... voids) {
final long startTime = System.currentTimeMillis();
final Map<Integer, Map<Integer, BatteryDiffData>> 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<Integer, Map<Integer, BatteryDiffData>> 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<Long, Map<String, BatteryHistEntry>> 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<Long, Map<String, BatteryHistEntry>> 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;
}
}

View File

@@ -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<Long, Map<String, BatteryHistEntry>> 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<Long, Map<String, BatteryHistEntry>> 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<Long, Map<String, BatteryHistEntry>> getHistoryMapWithExpectedTimestamps(
Context context,
final Map<Long, Map<String, BatteryHistEntry>> 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<Integer, Map<Integer, BatteryDiffData>> getBatteryUsageMapFromStatsService(
final Context context) {
final Map<Integer, Map<Integer, BatteryDiffData>> resultMap = new HashMap<>();
final Map<Integer, BatteryDiffData> 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<Integer, Map<Integer, BatteryDiffData>> 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<Integer, Map<Integer, BatteryDiffData>> getBatteryUsageMapFromStatsService(
final Context context) {
final Map<Integer, Map<Integer, BatteryDiffData>> resultMap = new HashMap<>();
final Map<Integer, BatteryDiffData> 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<BatteryHistEntry> getBatteryHistListFromFromStatsService(
final Context context) {
@@ -1469,22 +1427,6 @@ public final class DataProcessor {
return true;
}
private static void loadLabelAndIcon(
@Nullable final Map<Integer, Map<Integer, BatteryDiffData>> 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<Void, Void, Map<Integer, Map<Integer, BatteryDiffData>>> {
Context mApplicationContext;
@@ -1536,7 +1478,7 @@ public final class DataProcessor {
private List<BatteryLevelData.PeriodBatteryLevelData> mHourlyBatteryLevelsPerDay;
private Map<Long, Map<String, BatteryHistEntry>> 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<Integer, Map<Integer, BatteryDiffData>> 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<Integer, Map<Integer, BatteryDiffData>> batteryUsageMap =
getBatteryUsageMapFromStatsService(mApplicationContext);
loadLabelAndIcon(batteryUsageMap);
Log.d(TAG, String.format("execute LoadUsageMapFromBatteryStatsServiceTask in %d/ms",
(System.currentTimeMillis() - startTime)));
return batteryUsageMap;
}
}
}