Update database clear & job refresh mechanism for time change intent

- Ignore time change intent for time format update
- Clear data after current time in DB and refresh periodic job
- Take a snapshot of current battery usage stats if no periodic job in DB

Bug: 336423923
Bug: 314921894
Fix: 314921894
Test: atest SettingsRoboTests:com.android.settings.fuelgauge.batteryusagei
Change-Id: Iec0f5e8e97f18c4603de711a5884336ba0af23a9
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java b/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java
index e407c63..5fa04eb 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java
@@ -67,7 +67,7 @@
                 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;
             default:
diff --git a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java
index a41e9bd..b40f71a 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/DatabaseUtils.java
@@ -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;
@@ -436,6 +434,23 @@
                 });
     }
 
+    /** 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 +471,14 @@
                 });
     }
 
-    /** 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(
@@ -861,36 +876,23 @@
     }
 
     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;
-        final String logInfo =
-                String.format(
-                        Locale.ENGLISH,
-                        "clear database = %b, current time = %d, last record time = %d",
-                        isOutOfTimeRange,
-                        currentTime,
-                        lastRecordTimestamp);
-        Log.d(TAG, logInfo);
-        BatteryUsageLogUtils.writeLog(context, Action.TIME_UPDATED, logInfo);
-        if (isOutOfTimeRange) {
-            DatabaseUtils.clearAll(context);
-            PeriodicJobManager.getInstance(context)
-                    .refreshJob(/* fromBoot= */ false);
+        if (batteryLevelRecordEvents.isEmpty()) {
+            // Take a snapshot of battery usage data immediately if there's no battery events.
+            BatteryUsageDataLoader.enqueueWork(context, /* isFullChargeStart= */ true);
         }
     }
 
diff --git a/src/com/android/settings/fuelgauge/batteryusage/db/AppUsageEventDao.java b/src/com/android/settings/fuelgauge/batteryusage/db/AppUsageEventDao.java
index d220b15..2497801 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/db/AppUsageEventDao.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/db/AppUsageEventDao.java
@@ -55,6 +55,10 @@
     @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();
diff --git a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryEventDao.java b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryEventDao.java
index 8b696fe..19d2043 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryEventDao.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryEventDao.java
@@ -65,6 +65,10 @@
     @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();
diff --git a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDao.java b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDao.java
index 520c6be..049251e 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDao.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryStateDao.java
@@ -61,6 +61,10 @@
     @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();
diff --git a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryUsageSlotDao.java b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryUsageSlotDao.java
index d8cf41d..d53b0cf 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/db/BatteryUsageSlotDao.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/db/BatteryUsageSlotDao.java
@@ -52,6 +52,10 @@
     @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();
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java
index df330a3..3e53b03 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java
@@ -35,7 +35,6 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.robolectric.RobolectricTestRunner;
@@ -64,9 +63,8 @@
 
         // Inserts fake data into database for testing.
         final BatteryStateDatabase database = BatteryTestUtils.setUpBatteryStateDatabase(mContext);
-        BatteryTestUtils.insertDataToBatteryStateTable(
-                mContext, Clock.systemUTC().millis(), "com.android.systemui");
         mDao = database.batteryStateDao();
+        mDao.clearAll();
         clearSharedPreferences();
     }
 
@@ -129,10 +127,13 @@
         assertThat(mShadowAlarmManager.peekNextScheduledAlarm()).isNull();
     }
 
-    @Ignore("b/314921894")
     @Test
-    public void onReceive_withTimeChangedIntent_clearsAllDataAndRefreshesJob()
+    public void onReceive_withTimeChangedIntentSetEarlierTime_refreshesJob()
             throws InterruptedException {
+        BatteryTestUtils.insertDataToBatteryStateTable(
+                mContext, Clock.systemUTC().millis() + 60000, "com.android.systemui");
+        assertThat(mDao.getAllAfter(0).size()).isEqualTo(1);
+
         mReceiver.onReceive(mContext, new Intent(Intent.ACTION_TIME_CHANGED));
 
         TimeUnit.MILLISECONDS.sleep(100);
@@ -141,6 +142,38 @@
     }
 
     @Test
+    public void onReceive_withTimeChangedIntentSetLaterTime_clearNoDataAndRefreshesJob()
+            throws InterruptedException {
+        BatteryTestUtils.insertDataToBatteryStateTable(
+                mContext, Clock.systemUTC().millis() - 60000, "com.android.systemui");
+        assertThat(mDao.getAllAfter(0).size()).isEqualTo(1);
+
+        mReceiver.onReceive(mContext, new Intent(Intent.ACTION_TIME_CHANGED));
+
+        TimeUnit.MILLISECONDS.sleep(100);
+        assertThat(mDao.getAllAfter(0).size()).isEqualTo(1);
+        assertThat(mShadowAlarmManager.peekNextScheduledAlarm()).isNotNull();
+    }
+
+    @Test
+    public void onReceive_withTimeFormatChangedIntent_skipRefreshJob() throws InterruptedException {
+        BatteryTestUtils.insertDataToBatteryStateTable(
+                mContext, Clock.systemUTC().millis() + 60000, "com.android.systemui");
+        assertThat(mDao.getAllAfter(0).size()).isEqualTo(1);
+
+        mReceiver.onReceive(
+                mContext,
+                new Intent(Intent.EXTRA_INTENT)
+                        .putExtra(
+                                Intent.EXTRA_TIME_PREF_24_HOUR_FORMAT,
+                                Intent.EXTRA_TIME_PREF_VALUE_USE_12_HOUR));
+
+        TimeUnit.MILLISECONDS.sleep(100);
+        assertThat(mDao.getAllAfter(0).size()).isEqualTo(1);
+        assertThat(mShadowAlarmManager.peekNextScheduledAlarm()).isNull();
+    }
+
+    @Test
     public void invokeJobRecheck_broadcastsIntent() {
         BootBroadcastReceiver.invokeJobRecheck(mContext);