Merge changes from topic "timestamp_update" into main

* changes:
  Update database clear & job refresh mechanism for time zone change intent
  Update database clear & job refresh mechanism for time change intent
  Update time format for the first timestamp on usage chartview.
This commit is contained in:
YK Hung
2024-05-10 04:46:11 +00:00
committed by Android (Google) Code Review
14 changed files with 238 additions and 68 deletions

View File

@@ -649,9 +649,7 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
private final class HourlyChartLabelTextGenerator extends BaseLabelTextGenerator
implements BatteryChartViewModel.LabelTextGenerator {
private static final int FULL_CHARGE_BATTERY_LEVEL = 100;
private boolean mIsFromFullCharge;
private boolean mIsStartTimestamp;
private long mFistTimestamp;
private long mLatestTimestamp;
@@ -664,7 +662,7 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
long timestamp = timestamps.get(index);
boolean showMinute = false;
if (Objects.equal(timestamp, mFistTimestamp)) {
if (mIsFromFullCharge) {
if (mIsStartTimestamp) {
showMinute = true;
} else {
// starts from 7 days ago
@@ -699,8 +697,7 @@ public class BatteryChartPreferenceController extends AbstractPreferenceControll
@NonNull final BatteryLevelData batteryLevelData) {
BatteryLevelData.PeriodBatteryLevelData firstDayLevelData =
batteryLevelData.getHourlyBatteryLevelsPerDay().get(0);
this.mIsFromFullCharge =
firstDayLevelData.getLevels().get(0) == FULL_CHARGE_BATTERY_LEVEL;
this.mIsStartTimestamp = firstDayLevelData.isStartTimestamp();
this.mFistTimestamp = firstDayLevelData.getTimestamps().get(0);
this.mLatestTimestamp =
getLast(

View File

@@ -28,6 +28,7 @@ import androidx.annotation.VisibleForTesting;
import androidx.core.util.Preconditions;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
@@ -39,17 +40,24 @@ public final class BatteryLevelData {
private static final long MIN_SIZE = 2;
private static final long TIME_SLOT = DateUtils.HOUR_IN_MILLIS * 2;
// For testing only.
@VisibleForTesting @Nullable static Calendar sTestCalendar;
/** A container for the battery timestamp and level data. */
public static final class PeriodBatteryLevelData {
// 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;
private final boolean mIsStartTimestamp;
public PeriodBatteryLevelData(
@NonNull Map<Long, Integer> batteryLevelMap, @NonNull List<Long> timestamps) {
@NonNull Map<Long, Integer> batteryLevelMap,
@NonNull List<Long> timestamps,
boolean isStartTimestamp) {
mTimestamps = timestamps;
mLevels = new ArrayList<>(timestamps.size());
mIsStartTimestamp = isStartTimestamp;
for (Long timestamp : timestamps) {
mLevels.add(
batteryLevelMap.containsKey(timestamp)
@@ -66,6 +74,10 @@ public final class BatteryLevelData {
return mLevels;
}
public boolean isStartTimestamp() {
return mIsStartTimestamp;
}
@Override
public String toString() {
return String.format(
@@ -105,14 +117,21 @@ public final class BatteryLevelData {
final List<Long> timestampList = new ArrayList<>(batteryLevelMap.keySet());
Collections.sort(timestampList);
final long minTimestamp = timestampList.get(0);
final long sixDaysAgoTimestamp =
DatabaseUtils.getTimestampSixDaysAgo(sTestCalendar != null ? sTestCalendar : null);
final boolean isStartTimestamp = minTimestamp > sixDaysAgoTimestamp;
final List<Long> dailyTimestamps = getDailyTimestamps(timestampList);
final List<List<Long>> hourlyTimestamps = getHourlyTimestamps(dailyTimestamps);
mDailyBatteryLevels = new PeriodBatteryLevelData(batteryLevelMap, dailyTimestamps);
mDailyBatteryLevels =
new PeriodBatteryLevelData(batteryLevelMap, dailyTimestamps, isStartTimestamp);
mHourlyBatteryLevelsPerDay = new ArrayList<>(hourlyTimestamps.size());
for (List<Long> hourlyTimestampsPerDay : hourlyTimestamps) {
for (int i = 0; i < hourlyTimestamps.size(); i++) {
final List<Long> hourlyTimestampsPerDay = hourlyTimestamps.get(i);
mHourlyBatteryLevelsPerDay.add(
new PeriodBatteryLevelData(batteryLevelMap, hourlyTimestampsPerDay));
new PeriodBatteryLevelData(
batteryLevelMap, hourlyTimestampsPerDay, isStartTimestamp && i == 0));
}
}

View File

@@ -67,9 +67,13 @@ public final class BootBroadcastReceiver extends BroadcastReceiver {
refreshJobs(context);
break;
case Intent.ACTION_TIME_CHANGED:
Log.d(TAG, "refresh job and clear all data from action=" + action);
Log.d(TAG, "refresh job and clear data from action=" + action);
DatabaseUtils.clearDataAfterTimeChangedIfNeeded(context, intent);
break;
case Intent.ACTION_TIMEZONE_CHANGED:
Log.d(TAG, "refresh job and clear all data from action=" + action);
DatabaseUtils.clearDataAfterTimeZoneChangedIfNeeded(context);
break;
default:
Log.w(TAG, "receive unsupported action=" + action);
}

View File

@@ -16,8 +16,6 @@
package com.android.settings.fuelgauge.batteryusage;
import static android.content.Intent.FLAG_RECEIVER_REPLACE_PENDING;
import static com.android.settings.fuelgauge.batteryusage.ConvertUtils.utcToLocalTimeForLogging;
import android.app.usage.IUsageStatsManager;
@@ -59,6 +57,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@@ -436,6 +435,23 @@ public final class DatabaseUtils {
});
}
/** Clears data after a specific startTimestamp in the battery usage database. */
public static void clearAllAfter(Context context, long startTimestamp) {
AsyncTask.execute(
() -> {
try {
final BatteryStateDatabase database =
BatteryStateDatabase.getInstance(context.getApplicationContext());
database.appUsageEventDao().clearAllAfter(startTimestamp);
database.batteryEventDao().clearAllAfter(startTimestamp);
database.batteryStateDao().clearAllAfter(startTimestamp);
database.batteryUsageSlotDao().clearAllAfter(startTimestamp);
} catch (RuntimeException e) {
Log.e(TAG, "clearAllAfter() failed", e);
}
});
}
/** Clears all out-of-date data in the battery usage database. */
public static void clearExpiredDataIfNeeded(Context context) {
AsyncTask.execute(
@@ -456,14 +472,14 @@ public final class DatabaseUtils {
});
}
/** Clears all data and jobs if current timestamp is out of the range of last recorded job. */
/** Clears data after new updated time and refresh periodic job. */
public static void clearDataAfterTimeChangedIfNeeded(Context context, Intent intent) {
if ((intent.getFlags() & FLAG_RECEIVER_REPLACE_PENDING) != 0) {
if ((intent.hasExtra(Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT))) {
BatteryUsageLogUtils.writeLog(
context,
Action.TIME_UPDATED,
"Database is not cleared because the time change intent is only"
+ " for the existing pending receiver.");
"Database is not cleared because the time change intent is"
+ " for time format change");
return;
}
AsyncTask.execute(
@@ -480,6 +496,22 @@ public final class DatabaseUtils {
});
}
/** Clears all data and reset jobs if timezone changed. */
public static void clearDataAfterTimeZoneChangedIfNeeded(Context context) {
AsyncTask.execute(
() -> {
try {
clearDataAfterTimeZoneChangedIfNeededInternal(context);
} catch (RuntimeException e) {
Log.e(TAG, "clearDataAfterTimeZoneChangedIfNeeded() failed", e);
BatteryUsageLogUtils.writeLog(
context,
Action.TIMEZONE_UPDATED,
"clearDataAfterTimeZoneChangedIfNeeded() failed" + e);
}
});
}
/** Returns the timestamp for 00:00 6 days before the calendar date. */
public static long getTimestampSixDaysAgo(Calendar calendar) {
Calendar startCalendar =
@@ -861,37 +893,38 @@ public final class DatabaseUtils {
}
private static void clearDataAfterTimeChangedIfNeededInternal(Context context) {
final long currentTime = System.currentTimeMillis();
final String logInfo =
String.format(Locale.ENGLISH, "clear data after current time = %d", currentTime);
Log.d(TAG, logInfo);
BatteryUsageLogUtils.writeLog(context, Action.TIME_UPDATED, logInfo);
DatabaseUtils.clearAllAfter(context, currentTime);
PeriodicJobManager.getInstance(context).refreshJob(/* fromBoot= */ false);
final List<BatteryEvent> batteryLevelRecordEvents =
DatabaseUtils.getBatteryEvents(
context,
Calendar.getInstance(),
getLastFullChargeTime(context),
BATTERY_LEVEL_RECORD_EVENTS);
final long lastRecordTimestamp =
batteryLevelRecordEvents.isEmpty()
? INVALID_TIMESTAMP
: batteryLevelRecordEvents.get(0).getTimestamp();
final long nextRecordTimestamp =
TimestampUtils.getNextEvenHourTimestamp(lastRecordTimestamp);
final long currentTime = System.currentTimeMillis();
final boolean isOutOfTimeRange =
lastRecordTimestamp == INVALID_TIMESTAMP
|| currentTime < lastRecordTimestamp
|| currentTime > nextRecordTimestamp;
if (batteryLevelRecordEvents.isEmpty()) {
// Take a snapshot of battery usage data immediately if there's no battery events.
BatteryUsageDataLoader.enqueueWork(context, /* isFullChargeStart= */ true);
}
}
private static void clearDataAfterTimeZoneChangedIfNeededInternal(Context context) {
final String logInfo =
String.format(
Locale.ENGLISH,
"clear database = %b, current time = %d, last record time = %d",
isOutOfTimeRange,
currentTime,
lastRecordTimestamp);
"clear database for new time zone = %s",
TimeZone.getDefault().toString());
BatteryUsageLogUtils.writeLog(context, Action.TIMEZONE_UPDATED, logInfo);
Log.d(TAG, logInfo);
BatteryUsageLogUtils.writeLog(context, Action.TIME_UPDATED, logInfo);
if (isOutOfTimeRange) {
DatabaseUtils.clearAll(context);
PeriodicJobManager.getInstance(context)
.refreshJob(/* fromBoot= */ false);
}
DatabaseUtils.clearAll(context);
PeriodicJobManager.getInstance(context).refreshJob(/* fromBoot= */ false);
// Take a snapshot of battery usage data immediately
BatteryUsageDataLoader.enqueueWork(context, /* isFullChargeStart= */ true);
}
private static long loadLongFromContentProvider(

View File

@@ -55,6 +55,10 @@ public interface AppUsageEventDao {
@Query("DELETE FROM AppUsageEventEntity WHERE timestamp <= :timestamp")
void clearAllBefore(long timestamp);
/** Deletes all recorded data after a specific timestamp. */
@Query("DELETE FROM AppUsageEventEntity WHERE timestamp >= :timestamp")
void clearAllAfter(long timestamp);
/** Clears all recorded data in the database. */
@Query("DELETE FROM AppUsageEventEntity")
void clearAll();

View File

@@ -65,6 +65,10 @@ public interface BatteryEventDao {
@Query("DELETE FROM BatteryEventEntity WHERE timestamp <= :timestamp")
void clearAllBefore(long timestamp);
/** Deletes all recorded data after a specific timestamp. */
@Query("DELETE FROM BatteryEventEntity WHERE timestamp >= :timestamp")
void clearAllAfter(long timestamp);
/** Clears all recorded data in the database. */
@Query("DELETE FROM BatteryEventEntity")
void clearAll();

View File

@@ -61,6 +61,10 @@ public interface BatteryStateDao {
@Query("DELETE FROM BatteryState WHERE timestamp <= :timestamp")
void clearAllBefore(long timestamp);
/** Deletes all recorded data after a specific timestamp. */
@Query("DELETE FROM BatteryState WHERE timestamp >= :timestamp")
void clearAllAfter(long timestamp);
/** Clears all recorded data in the database. */
@Query("DELETE FROM BatteryState")
void clearAll();

View File

@@ -52,6 +52,10 @@ public interface BatteryUsageSlotDao {
@Query("DELETE FROM BatteryUsageSlotEntity WHERE timestamp <= :timestamp")
void clearAllBefore(long timestamp);
/** Deletes all recorded data after a specific timestamp. */
@Query("DELETE FROM BatteryUsageSlotEntity WHERE timestamp >= :timestamp")
void clearAllAfter(long timestamp);
/** Clears all recorded data in the database. */
@Query("DELETE FROM BatteryUsageSlotEntity")
void clearAll();