Fix b/273175976: Screen time counts the time before full charge.

Use the raw start timestamp instead of the first timestamp in the level
map to query app usage time.

Bug: 273175976
Fix: 273175976
Test: manual
Change-Id: Idb43b2bd5378e2f34ec722354408754f4a439c6d
This commit is contained in:
Zaiyue Xue
2023-03-13 15:34:28 +08:00
parent 3a4c6feb0a
commit a528f1e382
5 changed files with 27 additions and 71 deletions

View File

@@ -77,10 +77,8 @@ public class DataProcessManager {
private List<BatteryLevelData.PeriodBatteryLevelData> mHourlyBatteryLevelsPerDay; private List<BatteryLevelData.PeriodBatteryLevelData> mHourlyBatteryLevelsPerDay;
private Map<Long, Map<String, BatteryHistEntry>> mBatteryHistoryMap; private Map<Long, Map<String, BatteryHistEntry>> mBatteryHistoryMap;
// The start timestamp of battery level data. As we don't know when is the full charge cycle // Raw start timestamp with round to the nearest hour.
// start time when loading app usage data, this value is used as the start time of querying app private long mRawStartTimestamp;
// usage data.
private long mStartTimestampOfLevelData;
private boolean mIsCurrentBatteryHistoryLoaded = false; private boolean mIsCurrentBatteryHistoryLoaded = false;
private boolean mIsCurrentAppUsageLoaded = false; private boolean mIsCurrentAppUsageLoaded = false;
@@ -105,6 +103,7 @@ public class DataProcessManager {
DataProcessManager( DataProcessManager(
Context context, Context context,
Handler handler, Handler handler,
final long rawStartTimestamp,
@NonNull final DataProcessor.UsageMapAsyncResponse callbackFunction, @NonNull final DataProcessor.UsageMapAsyncResponse callbackFunction,
@NonNull final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay, @NonNull final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay,
@NonNull final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) { @NonNull final Map<Long, Map<String, BatteryHistEntry>> batteryHistoryMap) {
@@ -114,7 +113,7 @@ public class DataProcessManager {
mCallbackFunction = callbackFunction; mCallbackFunction = callbackFunction;
mHourlyBatteryLevelsPerDay = hourlyBatteryLevelsPerDay; mHourlyBatteryLevelsPerDay = hourlyBatteryLevelsPerDay;
mBatteryHistoryMap = batteryHistoryMap; mBatteryHistoryMap = batteryHistoryMap;
mStartTimestampOfLevelData = getStartTimestampOfBatteryLevelData(); mRawStartTimestamp = rawStartTimestamp;
} }
/** /**
@@ -153,21 +152,6 @@ public class DataProcessManager {
} }
} }
@VisibleForTesting
long getStartTimestampOfBatteryLevelData() {
for (int dailyIndex = 0; dailyIndex < mHourlyBatteryLevelsPerDay.size(); dailyIndex++) {
if (mHourlyBatteryLevelsPerDay.get(dailyIndex) == null) {
continue;
}
final List<Long> timestamps =
mHourlyBatteryLevelsPerDay.get(dailyIndex).getTimestamps();
if (timestamps.size() > 0) {
return timestamps.get(0);
}
}
return 0;
}
@VisibleForTesting @VisibleForTesting
List<AppUsageEvent> getAppUsageEventList() { List<AppUsageEvent> getAppUsageEventList() {
return mAppUsageEventList; return mAppUsageEventList;
@@ -251,7 +235,7 @@ public class DataProcessManager {
final int workProfileUserId = getWorkProfileUserId(); final int workProfileUserId = getWorkProfileUserId();
final UsageEvents usageEventsForCurrentUser = final UsageEvents usageEventsForCurrentUser =
DataProcessor.getAppUsageEventsForUser( DataProcessor.getAppUsageEventsForUser(
mContext, currentUserId, mStartTimestampOfLevelData); mContext, currentUserId, mRawStartTimestamp);
// If fail to load usage events for current user, return null directly and screen-on // If fail to load usage events for current user, return null directly and screen-on
// time will not be shown in the UI. // time will not be shown in the UI.
if (usageEventsForCurrentUser == null) { if (usageEventsForCurrentUser == null) {
@@ -262,7 +246,7 @@ public class DataProcessManager {
if (workProfileUserId != Integer.MIN_VALUE) { if (workProfileUserId != Integer.MIN_VALUE) {
usageEventsForWorkProfile = usageEventsForWorkProfile =
DataProcessor.getAppUsageEventsForUser( DataProcessor.getAppUsageEventsForUser(
mContext, workProfileUserId, mStartTimestampOfLevelData); mContext, workProfileUserId, mRawStartTimestamp);
} else { } else {
Log.d(TAG, "there is no work profile"); Log.d(TAG, "there is no work profile");
} }
@@ -309,7 +293,7 @@ public class DataProcessManager {
final List<AppUsageEvent> appUsageEventList = final List<AppUsageEvent> appUsageEventList =
DatabaseUtils.getAppUsageEventForUsers( DatabaseUtils.getAppUsageEventForUsers(
mContext, Calendar.getInstance(), getCurrentUserIds(), mContext, Calendar.getInstance(), getCurrentUserIds(),
mStartTimestampOfLevelData); mRawStartTimestamp);
Log.d(TAG, String.format("execute loadDatabaseAppUsageList size=%d in %d/ms", Log.d(TAG, String.format("execute loadDatabaseAppUsageList size=%d in %d/ms",
appUsageEventList.size(), (System.currentTimeMillis() - startTime))); appUsageEventList.size(), (System.currentTimeMillis() - startTime)));
return appUsageEventList; return appUsageEventList;
@@ -376,7 +360,7 @@ public class DataProcessManager {
// Generates the indexed AppUsagePeriod list data for each corresponding time slot for // Generates the indexed AppUsagePeriod list data for each corresponding time slot for
// further use. // further use.
mAppUsagePeriodMap = DataProcessor.generateAppUsagePeriodMap( mAppUsagePeriodMap = DataProcessor.generateAppUsagePeriodMap(
mHourlyBatteryLevelsPerDay, mAppUsageEventList); mRawStartTimestamp, mHourlyBatteryLevelsPerDay, mAppUsageEventList);
} }
private void tryToGenerateFinalDataAndApplyCallback() { private void tryToGenerateFinalDataAndApplyCallback() {
@@ -489,10 +473,13 @@ public class DataProcessManager {
return null; return null;
} }
final long rawStartTimestamp =
batteryHistoryMap.keySet().stream().min(Long::compare).orElse(0L);
// Start the async task to compute diff usage data and load labels and icons. // Start the async task to compute diff usage data and load labels and icons.
new DataProcessManager( new DataProcessManager(
context, context,
handler, handler,
rawStartTimestamp,
asyncResponseDelegate, asyncResponseDelegate,
batteryLevelData.getHourlyBatteryLevelsPerDay(), batteryLevelData.getHourlyBatteryLevelsPerDay(),
processedBatteryHistoryMap).start(); processedBatteryHistoryMap).start();

View File

@@ -265,8 +265,9 @@ public final class DataProcessor {
@Nullable @Nullable
public static Map<Integer, Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>>> public static Map<Integer, Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>>>
generateAppUsagePeriodMap( generateAppUsagePeriodMap(
final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay, final long rawStartTimestamp,
final List<AppUsageEvent> appUsageEventList) { final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay,
final List<AppUsageEvent> appUsageEventList) {
if (appUsageEventList.isEmpty()) { if (appUsageEventList.isEmpty()) {
Log.w(TAG, "appUsageEventList is empty"); Log.w(TAG, "appUsageEventList is empty");
return null; return null;
@@ -288,8 +289,12 @@ public final class DataProcessor {
final List<Long> timestamps = hourlyBatteryLevelsPerDay.get(dailyIndex).getTimestamps(); final List<Long> timestamps = hourlyBatteryLevelsPerDay.get(dailyIndex).getTimestamps();
final long hourlySize = timestamps.size() - 1; final long hourlySize = timestamps.size() - 1;
for (int hourlyIndex = 0; hourlyIndex < timestamps.size() - 1; hourlyIndex++) { for (int hourlyIndex = 0; hourlyIndex < timestamps.size() - 1; hourlyIndex++) {
// The start and end timestamps of this slot should be the adjacent timestamps. // The start slot timestape is the near hour timestamp instead of the last full
final long startTimestamp = timestamps.get(hourlyIndex); // charge time. So use rawStartTimestamp instead of reading the timestamp from
// hourlyBatteryLevelsPerDay here.
final long startTimestamp =
dailyIndex == 0 && hourlyIndex == 0 && !sDebug
? rawStartTimestamp : timestamps.get(hourlyIndex);
// The final slot is to show the data from last even hour until now but the // The final slot is to show the data from last even hour until now but the
// timestamp in hourlyBatteryLevelsPerDay is not the real value. So use current // timestamp in hourlyBatteryLevelsPerDay is not the real value. So use current
// timestamp instead of reading the timestamp from hourlyBatteryLevelsPerDay here. // timestamp instead of reading the timestamp from hourlyBatteryLevelsPerDay here.

View File

@@ -139,13 +139,13 @@ public final class DatabaseUtils {
Context context, Context context,
final Calendar calendar, final Calendar calendar,
final List<Integer> userIds, final List<Integer> userIds,
final long startTimestampOfLevelData) { final long rawStartTimestamp) {
final long startTime = System.currentTimeMillis(); final long startTime = System.currentTimeMillis();
final long sixDaysAgoTimestamp = getTimestampSixDaysAgo(calendar); final long sixDaysAgoTimestamp = getTimestampSixDaysAgo(calendar);
// Query a longer time period and then trim to the original time period in order to make // Query a longer time period and then trim to the original time period in order to make
// sure the app usage calculation near the boundaries is correct. // sure the app usage calculation near the boundaries is correct.
final long queryTimestamp = final long queryTimestamp =
Math.max(startTimestampOfLevelData, sixDaysAgoTimestamp) - USAGE_QUERY_BUFFER_HOURS; Math.max(rawStartTimestamp, sixDaysAgoTimestamp) - USAGE_QUERY_BUFFER_HOURS;
Log.d(TAG, "sixDayAgoTimestamp: " + sixDaysAgoTimestamp); Log.d(TAG, "sixDayAgoTimestamp: " + sixDaysAgoTimestamp);
final String queryUserIdString = userIds.stream() final String queryUserIdString = userIds.stream()
.map(userId -> String.valueOf(userId)) .map(userId -> String.valueOf(userId))

View File

@@ -82,8 +82,8 @@ public final class DataProcessManagerTest {
doReturn(66).when(mIntent).getIntExtra(eq(BatteryManager.EXTRA_LEVEL), anyInt()); doReturn(66).when(mIntent).getIntExtra(eq(BatteryManager.EXTRA_LEVEL), anyInt());
mDataProcessManager = new DataProcessManager( mDataProcessManager = new DataProcessManager(
mContext, /*handler=*/ null, /*callbackFunction=*/ null, mContext, /*handler=*/ null, /*rawStartTimestamp=*/ 0L,
/*hourlyBatteryLevelsPerDay=*/ new ArrayList<>(), /*callbackFunction=*/ null, /*hourlyBatteryLevelsPerDay=*/ new ArrayList<>(),
/*batteryHistoryMap=*/ new HashMap<>()); /*batteryHistoryMap=*/ new HashMap<>());
} }
@@ -174,7 +174,7 @@ public final class DataProcessManagerTest {
DatabaseUtils.sFakeAppUsageEventSupplier = () -> cursor; DatabaseUtils.sFakeAppUsageEventSupplier = () -> cursor;
final DataProcessManager dataProcessManager = new DataProcessManager( final DataProcessManager dataProcessManager = new DataProcessManager(
mContext, /*handler=*/ null, /*callbackFunction=*/ null, mContext, /*handler=*/ null, /*rawStartTimestamp=*/ 2L, /*callbackFunction=*/ null,
hourlyBatteryLevelsPerDay, /*batteryHistoryMap=*/ new HashMap<>()); hourlyBatteryLevelsPerDay, /*batteryHistoryMap=*/ new HashMap<>());
dataProcessManager.start(); dataProcessManager.start();
@@ -249,42 +249,6 @@ public final class DataProcessManagerTest {
assertThat(mDataProcessManager.getShowScreenOnTime()).isFalse(); assertThat(mDataProcessManager.getShowScreenOnTime()).isFalse();
} }
@Test
public void getStartTimestampOfBatteryLevelData_returnExpectedResult() {
final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay =
new ArrayList<>();
final List<Long> timestamps = new ArrayList<>();
timestamps.add(101L);
timestamps.add(1001L);
final List<Integer> levels = new ArrayList<>();
levels.add(1);
levels.add(2);
hourlyBatteryLevelsPerDay.add(null);
hourlyBatteryLevelsPerDay.add(
new BatteryLevelData.PeriodBatteryLevelData(timestamps, levels));
final DataProcessManager dataProcessManager = new DataProcessManager(
mContext, /*handler=*/ null, /*callbackFunction=*/ null,
hourlyBatteryLevelsPerDay, /*batteryHistoryMap=*/ null);
assertThat(dataProcessManager.getStartTimestampOfBatteryLevelData()).isEqualTo(101);
}
@Test
public void getStartTimestampOfBatteryLevelData_emptyLevels_returnZero() {
final List<BatteryLevelData.PeriodBatteryLevelData> hourlyBatteryLevelsPerDay =
new ArrayList<>();
hourlyBatteryLevelsPerDay.add(null);
hourlyBatteryLevelsPerDay.add(
new BatteryLevelData.PeriodBatteryLevelData(new ArrayList<>(), new ArrayList<>()));
final DataProcessManager dataProcessManager = new DataProcessManager(
mContext, /*handler=*/ null, /*callbackFunction=*/ null,
hourlyBatteryLevelsPerDay, /*batteryHistoryMap=*/ null);
assertThat(dataProcessManager.getStartTimestampOfBatteryLevelData()).isEqualTo(0);
}
@Test @Test
public void getBatteryLevelData_emptyHistoryMap_returnNull() { public void getBatteryLevelData_emptyHistoryMap_returnNull() {
assertThat(DataProcessManager.getBatteryLevelData( assertThat(DataProcessManager.getBatteryLevelData(

View File

@@ -250,7 +250,7 @@ public final class DataProcessorTest {
final Map<Integer, Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>>> periodMap = final Map<Integer, Map<Integer, Map<Long, Map<String, List<AppUsagePeriod>>>>> periodMap =
DataProcessor.generateAppUsagePeriodMap( DataProcessor.generateAppUsagePeriodMap(
hourlyBatteryLevelsPerDay, appUsageEventList); 14400000L, hourlyBatteryLevelsPerDay, appUsageEventList);
assertThat(periodMap).hasSize(3); assertThat(periodMap).hasSize(3);
// Day 1 // Day 1
@@ -288,7 +288,7 @@ public final class DataProcessorTest {
hourlyBatteryLevelsPerDay.add( hourlyBatteryLevelsPerDay.add(
new BatteryLevelData.PeriodBatteryLevelData(new ArrayList<>(), new ArrayList<>())); new BatteryLevelData.PeriodBatteryLevelData(new ArrayList<>(), new ArrayList<>()));
assertThat(DataProcessor.generateAppUsagePeriodMap( assertThat(DataProcessor.generateAppUsagePeriodMap(
hourlyBatteryLevelsPerDay, new ArrayList<>())).isNull(); 0L, hourlyBatteryLevelsPerDay, new ArrayList<>())).isNull();
} }
@Test @Test