Add a sysprop to delay merge by fix duration

Merge is a power consuming process, some products might want to delay
merge after boot by some amount of time. Add a sysprop to configure how
many seconds the merge should be delayed. To prevent the device from
delaying merge indefinitely, this sysprop is capped at 10 minutes.

Bug: 311515963
Test: set ro.virtual_ab.merge_delay_seconds by PRODUCT_PROPERTY_OVERRIDES in mk files and run OTA
Change-Id: I2361dd47221552dbc774c00412c65a22f40bc11d
diff --git a/aosp/cleanup_previous_update_action.cc b/aosp/cleanup_previous_update_action.cc
index fb7bac1..b070ecd 100644
--- a/aosp/cleanup_previous_update_action.cc
+++ b/aosp/cleanup_previous_update_action.cc
@@ -15,6 +15,7 @@
 //
 #include "update_engine/aosp/cleanup_previous_update_action.h"
 
+#include <algorithm>
 #include <chrono>  // NOLINT(build/c++11) -- for merge times
 #include <functional>
 #include <string>
@@ -38,6 +39,8 @@
 using brillo::MessageLoop;
 
 constexpr char kBootCompletedProp[] = "sys.boot_completed";
+constexpr auto&& kMergeDelaySecondsProp = "ro.virtual_ab.merge_delay_seconds";
+constexpr size_t kMaxMergeDelaySeconds = 600;
 // Interval to check sys.boot_completed.
 constexpr auto kCheckBootCompletedInterval = base::TimeDelta::FromSeconds(2);
 // Interval to check IBootControl::isSlotMarkedSuccessful
@@ -108,16 +111,15 @@
 // future. This avoids StopActionInternal() from resetting task IDs in an
 // unexpected way because task IDs could be reused.
 void CleanupPreviousUpdateAction::AcknowledgeTaskExecuted() {
-  if (scheduled_task_ != MessageLoop::kTaskIdNull) {
+  if (scheduled_task_.IsScheduled()) {
     LOG(INFO) << "Executing task " << scheduled_task_;
   }
-  scheduled_task_ = MessageLoop::kTaskIdNull;
 }
 
 // Check that scheduled_task_ is a valid task ID. Otherwise, terminate the
 // action.
 void CleanupPreviousUpdateAction::CheckTaskScheduled(std::string_view name) {
-  if (scheduled_task_ == MessageLoop::kTaskIdNull) {
+  if (!scheduled_task_.IsScheduled()) {
     LOG(ERROR) << "Unable to schedule " << name;
     processor_->ActionComplete(this, ErrorCode::kError);
   } else {
@@ -130,8 +132,8 @@
   LOG(INFO) << "Stopping/suspending/completing CleanupPreviousUpdateAction";
   running_ = false;
 
-  if (scheduled_task_ != MessageLoop::kTaskIdNull) {
-    if (MessageLoop::current()->CancelTask(scheduled_task_)) {
+  if (scheduled_task_.IsScheduled()) {
+    if (scheduled_task_.Cancel()) {
       LOG(INFO) << "CleanupPreviousUpdateAction cancelled pending task ID "
                 << scheduled_task_;
     } else {
@@ -139,7 +141,6 @@
                  << scheduled_task_;
     }
   }
-  scheduled_task_ = MessageLoop::kTaskIdNull;
 }
 
 void CleanupPreviousUpdateAction::StartActionInternal() {
@@ -164,12 +165,13 @@
 
 void CleanupPreviousUpdateAction::ScheduleWaitBootCompleted() {
   TEST_AND_RETURN(running_);
-  scheduled_task_ = MessageLoop::current()->PostDelayedTask(
-      FROM_HERE,
-      base::Bind(&CleanupPreviousUpdateAction::WaitBootCompletedOrSchedule,
-                 base::Unretained(this)),
-      kCheckBootCompletedInterval);
-  CheckTaskScheduled("WaitBootCompleted");
+  if (!scheduled_task_.PostTask(
+          FROM_HERE,
+          base::Bind(&CleanupPreviousUpdateAction::WaitBootCompletedOrSchedule,
+                     base::Unretained(this)),
+          kCheckBootCompletedInterval)) {
+    CheckTaskScheduled("WaitBootCompleted");
+  }
 }
 
 void CleanupPreviousUpdateAction::WaitBootCompletedOrSchedule() {
@@ -192,13 +194,33 @@
 
 void CleanupPreviousUpdateAction::ScheduleWaitMarkBootSuccessful() {
   TEST_AND_RETURN(running_);
-  scheduled_task_ = MessageLoop::current()->PostDelayedTask(
-      FROM_HERE,
-      base::Bind(
-          &CleanupPreviousUpdateAction::CheckSlotMarkedSuccessfulOrSchedule,
-          base::Unretained(this)),
-      kCheckSlotMarkedSuccessfulInterval);
-  CheckTaskScheduled("WaitMarkBootSuccessful");
+  if (!scheduled_task_.PostTask(
+          FROM_HERE,
+          base::Bind(
+              &CleanupPreviousUpdateAction::CheckSlotMarkedSuccessfulOrSchedule,
+              base::Unretained(this)),
+          kCheckSlotMarkedSuccessfulInterval)) {
+    CheckTaskScheduled("WaitMarkBootSuccessful");
+  }
+}
+
+void CleanupPreviousUpdateAction::CheckForMergeDelay() {
+  const auto merge_delay_seconds =
+      std::clamp<int>(android::base::GetIntProperty(kMergeDelaySecondsProp, 0),
+                      0,
+                      kMaxMergeDelaySeconds);
+  if (merge_delay_seconds != 0) {
+    LOG(INFO) << "Merge is ready to start, but " << kMergeDelaySecondsProp
+              << " is set, delaying merge by " << merge_delay_seconds
+              << " seconds";
+  }
+  if (!scheduled_task_.PostTask(
+          FROM_HERE,
+          [this]() { StartMerge(); },
+          base::TimeDelta::FromSeconds(merge_delay_seconds))) {
+    LOG(ERROR) << "Unable to schedule " << __FUNCTION__;
+    processor_->ActionComplete(this, ErrorCode::kError);
+  }
 }
 
 void CleanupPreviousUpdateAction::CheckSlotMarkedSuccessfulOrSchedule() {
@@ -209,7 +231,10 @@
     ScheduleWaitMarkBootSuccessful();
     return;
   }
+  CheckForMergeDelay();
+}
 
+void CleanupPreviousUpdateAction::StartMerge() {
   if (metadata_device_ == nullptr) {
     metadata_device_ = snapshot_->EnsureMetadataMounted();
   }
@@ -265,12 +290,13 @@
 
 void CleanupPreviousUpdateAction::ScheduleWaitForMerge() {
   TEST_AND_RETURN(running_);
-  scheduled_task_ = MessageLoop::current()->PostDelayedTask(
-      FROM_HERE,
-      base::Bind(&CleanupPreviousUpdateAction::WaitForMergeOrSchedule,
-                 base::Unretained(this)),
-      kWaitForMergeInterval);
-  CheckTaskScheduled("WaitForMerge");
+  if (!scheduled_task_.PostTask(
+          FROM_HERE,
+          base::Bind(&CleanupPreviousUpdateAction::WaitForMergeOrSchedule,
+                     base::Unretained(this)),
+          kWaitForMergeInterval)) {
+    CheckTaskScheduled("WaitForMerge");
+  }
 }
 
 void CleanupPreviousUpdateAction::WaitForMergeOrSchedule() {
diff --git a/aosp/cleanup_previous_update_action.h b/aosp/cleanup_previous_update_action.h
index b93c557..1d701b1 100644
--- a/aosp/cleanup_previous_update_action.h
+++ b/aosp/cleanup_previous_update_action.h
@@ -31,6 +31,7 @@
 #include "update_engine/common/cleanup_previous_update_action_delegate.h"
 #include "update_engine/common/error_code.h"
 #include "update_engine/common/prefs_interface.h"
+#include "update_engine/common/scoped_task_id.h"
 
 namespace chromeos_update_engine {
 
@@ -76,7 +77,7 @@
   bool cancel_failed_{false};
   unsigned int last_percentage_{0};
   android::snapshot::ISnapshotMergeStats* merge_stats_;
-  brillo::MessageLoop::TaskId scheduled_task_{brillo::MessageLoop::kTaskIdNull};
+  ScopedTaskId scheduled_task_;
 
   // Helpers for task management.
   void AcknowledgeTaskExecuted();
@@ -88,6 +89,8 @@
   void WaitBootCompletedOrSchedule();
   void ScheduleWaitMarkBootSuccessful();
   void CheckSlotMarkedSuccessfulOrSchedule();
+  void CheckForMergeDelay();
+  void StartMerge();
   void ScheduleWaitForMerge();
   void WaitForMergeOrSchedule();
   void InitiateMergeAndWait();
diff --git a/common/scoped_task_id.h b/common/scoped_task_id.h
index 91a2986..8127d37 100644
--- a/common/scoped_task_id.h
+++ b/common/scoped_task_id.h
@@ -17,6 +17,7 @@
 #ifndef UPDATE_ENGINE_SCOPED_TASK_ID_H_
 #define UPDATE_ENGINE_SCOPED_TASK_ID_H_
 
+#include <ostream>
 #include <type_traits>
 #include <utility>
 
@@ -35,6 +36,11 @@
   ScopedTaskId(const ScopedTaskId&) = delete;
   ScopedTaskId& operator=(const ScopedTaskId&) = delete;
 
+  friend std::ostream& operator<<(std::ostream& out, const ScopedTaskId& task) {
+    out << task.task_id_;
+    return out;
+  }
+
   constexpr ScopedTaskId() = default;
 
   constexpr ScopedTaskId(ScopedTaskId&& other) noexcept {
@@ -89,7 +95,6 @@
     return task_id_ < other.task_id_;
   }
 
- private:
   template <typename Callable>
   [[nodiscard]] bool PostTask(const base::Location& from_here,
                               Callable&& callback,
@@ -107,11 +112,16 @@
         delay);
     return task_id_ != MessageLoop::kTaskIdNull;
   }
+
+ private:
   template <typename Callable>
   void ExecuteTask(Callable&& callback) {
     task_id_ = MessageLoop::kTaskIdNull;
     if constexpr (std::is_same_v<Callable&&, base::OnceClosure&&>) {
       std::move(callback).Run();
+    } else if constexpr (std::is_same_v<Callable&&,
+                                        base::RepeatingCallback<void()>&&>) {
+      std::move(callback).Run();
     } else {
       std::move(callback)();
     }