Cancel data loading when hourly job happens in the first 40 minutes
after booting in AOSP.

Test: make RunSettingsRoboTests + manual
Bug: 257384343
Change-Id: I437b68719ff5ece73fa33b74cb144f4262528e8c
diff --git a/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java b/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java
index 959820a..e51db08 100644
--- a/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java
+++ b/src/com/android/settings/fuelgauge/PowerUsageFeatureProvider.java
@@ -136,6 +136,11 @@
     boolean isExtraDefend();
 
     /**
+     * Returns {@code true} if delay the hourly job when device is booting.
+     */
+    boolean delayHourlyJobWhenBooting();
+
+    /**
      * Gets a intent for one time bypass charge limited to resume charging.
      */
     Intent getResumeChargeIntent(boolean isDockDefender);
diff --git a/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java b/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java
index 94e4872..ba8587c 100644
--- a/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java
+++ b/src/com/android/settings/fuelgauge/PowerUsageFeatureProviderImpl.java
@@ -157,6 +157,11 @@
     }
 
     @Override
+    public boolean delayHourlyJobWhenBooting() {
+        return true;
+    }
+
+    @Override
     public Set<CharSequence> getHideBackgroundUsageTimeSet(Context context) {
         return new ArraySet<>();
     }
diff --git a/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java b/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java
index c9d73cd..8c9285e 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiver.java
@@ -24,13 +24,17 @@
 import android.util.Log;
 
 import com.android.settings.core.instrumentation.ElapsedTimeUtils;
+import com.android.settings.overlay.FeatureFactory;
 
 import java.time.Duration;
 
 /** Receives broadcasts to start or stop the periodic fetching job. */
 public final class BootBroadcastReceiver extends BroadcastReceiver {
     private static final String TAG = "BootBroadcastReceiver";
-    private static final long RESCHEDULE_FOR_BOOT_ACTION = Duration.ofSeconds(6).toMillis();
+    private static final long RESCHEDULE_FOR_BOOT_ACTION_WITH_DELAY =
+            Duration.ofMinutes(40).toMillis();
+    private static final long RESCHEDULE_FOR_BOOT_ACTION_WITHOUT_DELAY =
+            Duration.ofSeconds(6).toMillis();
 
     private final Handler mHandler = new Handler(Looper.getMainLooper());
 
@@ -67,7 +71,7 @@
             case Intent.ACTION_TIME_CHANGED:
                 Log.d(TAG, "refresh job and clear all data from action=" + action);
                 DatabaseUtils.clearAll(context);
-                PeriodicJobManager.getInstance(context).refreshJob();
+                PeriodicJobManager.getInstance(context).refreshJob(/*fromBoot=*/ false);
                 break;
             default:
                 Log.w(TAG, "receive unsupported action=" + action);
@@ -78,15 +82,23 @@
             final Intent recheckIntent = new Intent(ACTION_PERIODIC_JOB_RECHECK);
             recheckIntent.setClass(context, BootBroadcastReceiver.class);
             mHandler.postDelayed(() -> context.sendBroadcast(recheckIntent),
-                    RESCHEDULE_FOR_BOOT_ACTION);
+                    getRescheduleTimeForBootAction(context));
         } else if (ACTION_SETUP_WIZARD_FINISHED.equals(action)) {
             ElapsedTimeUtils.storeSuwFinishedTimestamp(context, System.currentTimeMillis());
         }
     }
 
+    private long getRescheduleTimeForBootAction(Context context) {
+        final boolean delayHourlyJobWhenBooting =
+                FeatureFactory.getFactory(context)
+                        .getPowerUsageFeatureProvider(context)
+                        .delayHourlyJobWhenBooting();
+        return delayHourlyJobWhenBooting
+                ? RESCHEDULE_FOR_BOOT_ACTION_WITH_DELAY
+                : RESCHEDULE_FOR_BOOT_ACTION_WITHOUT_DELAY;
+    }
+
     private static void refreshJobs(Context context) {
-        // Clears useless data from battery usage database if needed.
-        DatabaseUtils.clearExpiredDataIfNeeded(context);
-        PeriodicJobManager.getInstance(context).refreshJob();
+        PeriodicJobManager.getInstance(context).refreshJob(/*fromBoot=*/ true);
     }
 }
diff --git a/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobManager.java b/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobManager.java
index 39293dc..2b18e92 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobManager.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobManager.java
@@ -24,6 +24,8 @@
 
 import androidx.annotation.VisibleForTesting;
 
+import com.android.settings.overlay.FeatureFactory;
+
 import java.text.SimpleDateFormat;
 import java.time.Clock;
 import java.time.Duration;
@@ -45,6 +47,9 @@
     @VisibleForTesting
     static final int DATA_FETCH_INTERVAL_MINUTE = 60;
 
+    @VisibleForTesting
+    static long sBroadcastDelayFromBoot = Duration.ofMinutes(40).toMillis();
+
     @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
     void reset() {
         sSingleton = null; // for testing only
@@ -65,7 +70,7 @@
 
     /** Schedules the next alarm job if it is available. */
     @SuppressWarnings("JavaUtilDate")
-    public void refreshJob() {
+    public void refreshJob(final boolean fromBoot) {
         if (mAlarmManager == null) {
             Log.e(TAG, "cannot schedule next alarm job");
             return;
@@ -74,7 +79,7 @@
         final PendingIntent pendingIntent = getPendingIntent();
         cancelJob(pendingIntent);
         // Uses UTC time to avoid scheduler is impacted by different timezone.
-        final long triggerAtMillis = getTriggerAtMillis(Clock.systemUTC());
+        final long triggerAtMillis = getTriggerAtMillis(mContext, Clock.systemUTC(), fromBoot);
         mAlarmManager.setExactAndAllowWhileIdle(
                 AlarmManager.RTC_WAKEUP, triggerAtMillis, pendingIntent);
         Log.d(TAG, "schedule next alarm job at "
@@ -90,11 +95,21 @@
     }
 
     /** Gets the next alarm trigger UTC time in milliseconds. */
-    static long getTriggerAtMillis(Clock clock) {
+    static long getTriggerAtMillis(Context context, Clock clock, final boolean fromBoot) {
         long currentTimeMillis = clock.millis();
+        final boolean delayHourlyJobWhenBooting =
+                FeatureFactory.getFactory(context)
+                        .getPowerUsageFeatureProvider(context)
+                        .delayHourlyJobWhenBooting();
         // Rounds to the previous nearest time slot and shifts to the next one.
         long timeSlotUnit = Duration.ofMinutes(DATA_FETCH_INTERVAL_MINUTE).toMillis();
-        return (currentTimeMillis / timeSlotUnit) * timeSlotUnit + timeSlotUnit;
+        long targetTime = (currentTimeMillis / timeSlotUnit) * timeSlotUnit + timeSlotUnit;
+        if (delayHourlyJobWhenBooting
+                && fromBoot
+                && (targetTime - currentTimeMillis) <= sBroadcastDelayFromBoot) {
+            targetTime += timeSlotUnit;
+        }
+        return targetTime;
     }
 
     private PendingIntent getPendingIntent() {
diff --git a/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiver.java b/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiver.java
index d6a2f62..3ca4532 100644
--- a/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiver.java
+++ b/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobReceiver.java
@@ -42,7 +42,7 @@
         BatteryUsageDataLoader.enqueueWork(context, /*isFullChargeStart=*/ false);
         AppUsageDataLoader.enqueueWork(context);
         Log.d(TAG, "refresh periodic job from action=" + action);
-        PeriodicJobManager.getInstance(context).refreshJob();
+        PeriodicJobManager.getInstance(context).refreshJob(/*fromBoot=*/ false);
         DatabaseUtils.clearExpiredDataIfNeeded(context);
     }
 }
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 514ac63..ba51e29 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/BootBroadcastReceiverTest.java
@@ -125,28 +125,6 @@
     }
 
     @Test
-    public void onReceive_containsExpiredData_clearsExpiredDataFromDatabase()
-            throws InterruptedException {
-        insertExpiredData(/*shiftDay=*/ DatabaseUtils.DATA_RETENTION_INTERVAL_DAY);
-
-        mReceiver.onReceive(mContext, new Intent(Intent.ACTION_BOOT_COMPLETED));
-
-        TimeUnit.MILLISECONDS.sleep(100);
-        assertThat(mDao.getAllAfter(0)).hasSize(1);
-    }
-
-    @Test
-    public void onReceive_withoutExpiredData_notClearsExpiredDataFromDatabase()
-            throws InterruptedException {
-        insertExpiredData(/*shiftDay=*/ DatabaseUtils.DATA_RETENTION_INTERVAL_DAY - 1);
-
-        mReceiver.onReceive(mContext, new Intent(Intent.ACTION_BOOT_COMPLETED));
-
-        TimeUnit.MILLISECONDS.sleep(100);
-        assertThat(mDao.getAllAfter(0)).hasSize(3);
-    }
-
-    @Test
     public void onReceive_withTimeChangedIntent_clearsAllDataAndRefreshesJob()
             throws InterruptedException {
         mReceiver.onReceive(mContext, new Intent(Intent.ACTION_TIME_CHANGED));
diff --git a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobManagerTest.java b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobManagerTest.java
index 9e27bb0..efbce12 100644
--- a/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobManagerTest.java
+++ b/tests/robotests/src/com/android/settings/fuelgauge/batteryusage/PeriodicJobManagerTest.java
@@ -57,7 +57,7 @@
 
     @Test
     public void refreshJob_refreshesAlarmJob() {
-        mPeriodicJobManager.refreshJob();
+        mPeriodicJobManager.refreshJob(/*fromBoot=*/ false);
 
         final ShadowAlarmManager.ScheduledAlarm alarm =
                 mShadowAlarmManager.peekNextScheduledAlarm();
@@ -76,7 +76,7 @@
         FakeClock fakeClock = new FakeClock();
         fakeClock.setCurrentTime(currentTimeDuration);
 
-        assertThat(PeriodicJobManager.getTriggerAtMillis(fakeClock))
+        assertThat(PeriodicJobManager.getTriggerAtMillis(mContext, fakeClock, /*fromBoot=*/ false))
                 .isEqualTo(currentTimeDuration.plusMinutes(timeSlotUnit).toMillis());
     }
 
@@ -89,7 +89,7 @@
         fakeClock.setCurrentTime(
                 currentTimeDuration.plusMinutes(1L).plusMillis(51L));
 
-        assertThat(PeriodicJobManager.getTriggerAtMillis(fakeClock))
+        assertThat(PeriodicJobManager.getTriggerAtMillis(mContext, fakeClock, /*fromBoot=*/ true))
                 .isEqualTo(currentTimeDuration.plusMinutes(timeSlotUnit).toMillis());
     }
 }