update_engine: Deferred update

This change adds in deferred update capability into update_engine.
Ref: http://go/deferredupdates

1. Consumer auto update policy is replaced by deferred update policy
 - This is to keep policy more generic + leave the implemenations of the
   policy to decide on how to enforce deferred updates.

2. Exposing `ApplyDeferredUpdate()` DBus API
 - Client tool will be allowed to call into this method for mainly
   testing/verification purposes.
 - Chrome will require this API in order actually "apply" the deferred
   update, once one is pending, hence is also exposed for `chronos` user
   as allowed to invoke.

3. Post installation will now handle hold and apply actions when there
   is a deferred update. Please reference changes in the installer and
   futility for both FW + OS side changes required to allow for deferred
   updates.

BUG=chromium:1278079, b:232304971
TEST=FEATURES=test emerge-$B update_engine update_engine-client
TEST=# autotest related to deferred updates.

Cq-Depend: chromium:3689993
Change-Id: I544a34d466619c5e1de346d34b01c84dec439285
Reviewed-on: https://chromium-review.googlesource.com/c/aosp/platform/system/update_engine/+/3689995
Reviewed-by: Henry Barnor <hbarnor@chromium.org>
Reviewed-by: Yuanpeng Ni‎ <yuanpengni@chromium.org>
Commit-Queue: Jae Hoon Kim <kimjae@chromium.org>
Tested-by: Jae Hoon Kim <kimjae@chromium.org>
diff --git a/BUILD.gn b/BUILD.gn
index 568c923..361ccd2 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -213,7 +213,7 @@
     "metrics_utils.cc",
     "update_boot_flags_action.cc",
     "update_manager/boxed_value.cc",
-    "update_manager/consumer_auto_update_policy_impl.cc",
+    "update_manager/deferred_update_policy_impl.cc",
     "update_manager/enough_slots_ab_updates_policy_impl.cc",
     "update_manager/enterprise_device_policy_impl.cc",
     "update_manager/enterprise_rollback_policy_impl.cc",
@@ -541,7 +541,7 @@
       "payload_generator/zip_unittest.cc",
       "update_boot_flags_action_unittest.cc",
       "update_manager/boxed_value_unittest.cc",
-      "update_manager/consumer_auto_update_policy_impl_unittest.cc",
+      "update_manager/deferred_update_policy_impl_unittest.cc",
       "update_manager/enterprise_device_policy_impl_unittest.cc",
       "update_manager/enterprise_rollback_policy_impl_unittest.cc",
       "update_manager/evaluation_context_unittest.cc",
diff --git a/UpdateEngine.conf b/UpdateEngine.conf
index ae22ada..0afb4c0 100644
--- a/UpdateEngine.conf
+++ b/UpdateEngine.conf
@@ -26,6 +26,9 @@
            send_member="Update"/>
     <allow send_destination="org.chromium.UpdateEngine"
            send_interface="org.chromium.UpdateEngineInterface"
+           send_member="ApplyDeferredUpdate"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
            send_member="AttemptRollback"/>
     <allow send_destination="org.chromium.UpdateEngine"
            send_interface="org.chromium.UpdateEngineInterface"
diff --git a/client_library/client_dbus.cc b/client_library/client_dbus.cc
index 90d6cc3..01992f8 100644
--- a/client_library/client_dbus.cc
+++ b/client_library/client_dbus.cc
@@ -82,6 +82,10 @@
   return proxy_->Update(update_params, nullptr);
 }
 
+bool DBusUpdateEngineClient::ApplyDeferredUpdate() {
+  return proxy_->ApplyDeferredUpdate(nullptr);
+}
+
 bool DBusUpdateEngineClient::AttemptInstall(const string& omaha_url,
                                             const vector<string>& dlc_ids) {
   return proxy_->AttemptInstall(omaha_url, dlc_ids, nullptr);
diff --git a/client_library/client_dbus.h b/client_library/client_dbus.h
index 0febcd6..48900e8 100644
--- a/client_library/client_dbus.h
+++ b/client_library/client_dbus.h
@@ -42,6 +42,8 @@
 
   bool Update(const update_engine::UpdateParams& update_params) override;
 
+  bool ApplyDeferredUpdate() override;
+
   bool AttemptInstall(const std::string& omaha_url,
                       const std::vector<std::string>& dlc_ids) override;
 
diff --git a/client_library/include/update_engine/client.h b/client_library/include/update_engine/client.h
index e8100f4..2ec2113 100644
--- a/client_library/include/update_engine/client.h
+++ b/client_library/include/update_engine/client.h
@@ -40,6 +40,9 @@
   //     Refer to proto defined in system_api.
   virtual bool Update(const update_engine::UpdateParams& update_params) = 0;
 
+  // Applies the deferred update if there is one.
+  virtual bool ApplyDeferredUpdate() = 0;
+
   // Request the update_engine to install a list of DLC modules.
   // |omaha_url|
   //     Force update_engine to look for updates from the given server. Passing
diff --git a/client_library/include/update_engine/update_status.h b/client_library/include/update_engine/update_status.h
index 3e1ed7a..2fd3a15 100644
--- a/client_library/include/update_engine/update_status.h
+++ b/client_library/include/update_engine/update_status.h
@@ -51,8 +51,9 @@
   // allow updates, e.g. over cellular network.
   NEED_PERMISSION_TO_UPDATE = 10,
   CLEANUP_PREVIOUS_UPDATE = 11,
+  UPDATED_BUT_DEFERRED = 12,
 
-  MAX = CLEANUP_PREVIOUS_UPDATE,
+  MAX = UPDATED_BUT_DEFERRED,
 
   // This value is exclusively used in Chrome. DO NOT define nor use it.
   // TODO(crbug.com/977320): Remove this value from chrome by refactoring the
diff --git a/common/boot_control_interface.h b/common/boot_control_interface.h
index 67d0284..18b4be6 100644
--- a/common/boot_control_interface.h
+++ b/common/boot_control_interface.h
@@ -55,6 +55,11 @@
   // and return kInvalidSlot.
   virtual Slot GetCurrentSlot() const = 0;
 
+  // Return the first slot where we are not running the system from. On success,
+  // the result is a number between 0 and GetNumSlots() - 1, will also not be
+  // equivalent to `GetCurrentSlot()`. Otherwise will return `kInvalidSlot`.
+  virtual Slot GetFirstInactiveSlot() const = 0;
+
   // Determines the block device for the given partition name and slot number.
   // The |slot| number must be between 0 and GetNumSlots() - 1 and the
   // |partition_name| is a platform-specific name that identifies a partition on
diff --git a/common/boot_control_stub.cc b/common/boot_control_stub.cc
index 2e9d0d5..37120a4 100644
--- a/common/boot_control_stub.cc
+++ b/common/boot_control_stub.cc
@@ -35,6 +35,11 @@
   return 0;
 }
 
+BootControlInterface::Slot BootControlStub::GetFirstInactiveSlot() const {
+  LOG(ERROR) << __FUNCTION__ << " should never be called.";
+  return 0;
+}
+
 bool BootControlStub::GetPartitionDevice(const std::string& partition_name,
                                          BootControlInterface::Slot slot,
                                          bool not_in_payload,
diff --git a/common/boot_control_stub.h b/common/boot_control_stub.h
index 0007dff..8c9df89 100644
--- a/common/boot_control_stub.h
+++ b/common/boot_control_stub.h
@@ -43,6 +43,7 @@
   // BootControlInterface overrides.
   unsigned int GetNumSlots() const override;
   BootControlInterface::Slot GetCurrentSlot() const override;
+  BootControlInterface::Slot GetFirstInactiveSlot() const override;
   bool GetPartitionDevice(const std::string& partition_name,
                           Slot slot,
                           bool not_in_payload,
diff --git a/common/constants.cc b/common/constants.cc
index 70ba74f..906da05 100644
--- a/common/constants.cc
+++ b/common/constants.cc
@@ -118,6 +118,7 @@
     "wall-clock-staging-wait-period";
 const char kPrefsManifestBytes[] = "manifest-bytes";
 const char kPrefsConsumerAutoUpdateDisabled[] = "consumer-auto-update-disabled";
+const char kPrefsDeferredUpdateCompleted[] = "deferred-update-completed";
 
 // These four fields are generated by scripts/brillo_update_payload.
 const char kPayloadPropertyFileSize[] = "FILE_SIZE";
diff --git a/common/constants.h b/common/constants.h
index cc45fcc..c3dac63 100644
--- a/common/constants.h
+++ b/common/constants.h
@@ -118,6 +118,7 @@
 extern const char kPrefsWallClockStagingWaitPeriod[];
 extern const char kPrefsManifestBytes[];
 extern const char kPrefsConsumerAutoUpdateDisabled[];
+extern const char kPrefsDeferredUpdateCompleted[];
 
 // Keys used when storing and loading payload properties.
 extern const char kPayloadPropertyFileSize[];
diff --git a/common/fake_boot_control.h b/common/fake_boot_control.h
index 184d86f..ebfd6e3 100644
--- a/common/fake_boot_control.h
+++ b/common/fake_boot_control.h
@@ -47,6 +47,9 @@
   BootControlInterface::Slot GetCurrentSlot() const override {
     return current_slot_;
   }
+  BootControlInterface::Slot GetFirstInactiveSlot() const override {
+    return first_inactive_slot_;
+  }
 
   bool GetPartitionDevice(const std::string& partition_name,
                           BootControlInterface::Slot slot,
@@ -110,6 +113,9 @@
   }
 
   void SetCurrentSlot(BootControlInterface::Slot slot) { current_slot_ = slot; }
+  void SetFirstInactiveSlot(BootControlInterface::Slot slot) {
+    first_inactive_slot_ = slot;
+  }
 
   void SetPartitionDevice(const std::string& partition_name,
                           BootControlInterface::Slot slot,
@@ -144,6 +150,7 @@
  private:
   BootControlInterface::Slot num_slots_{2};
   BootControlInterface::Slot current_slot_{0};
+  BootControlInterface::Slot first_inactive_slot_{0};
 
   std::vector<bool> is_bootable_;
   std::vector<bool> is_marked_successful_;
diff --git a/common/metrics_constants.h b/common/metrics_constants.h
index 49f0b5a..0261781 100644
--- a/common/metrics_constants.h
+++ b/common/metrics_constants.h
@@ -29,6 +29,7 @@
   kDownloadError,      // Error downloading response from Omaha.
   kParsingError,       // Error parsing response.
   kRebootPending,      // No update check was performed a reboot is pending.
+  kDeferredUpdate,     // Update is applied, but deferred.
 
   kNumConstants,
   kUnset = -1
diff --git a/cros/boot_control_chromeos.cc b/cros/boot_control_chromeos.cc
index a74e2a5..15ddf6a 100644
--- a/cros/boot_control_chromeos.cc
+++ b/cros/boot_control_chromeos.cc
@@ -191,6 +191,18 @@
   return current_slot_;
 }
 
+BootControlInterface::Slot BootControlChromeOS::GetFirstInactiveSlot() const {
+  if (GetCurrentSlot() == BootControlInterface::kInvalidSlot ||
+      GetNumSlots() < 2)
+    return BootControlInterface::kInvalidSlot;
+
+  for (Slot slot = 0; slot < GetNumSlots(); slot++) {
+    if (slot != GetCurrentSlot())
+      return slot;
+  }
+  return BootControlInterface::kInvalidSlot;
+}
+
 bool BootControlChromeOS::ParseDlcPartitionName(
     const std::string partition_name,
     std::string* dlc_id,
diff --git a/cros/boot_control_chromeos.h b/cros/boot_control_chromeos.h
index 3d72463..12c9b3d 100644
--- a/cros/boot_control_chromeos.h
+++ b/cros/boot_control_chromeos.h
@@ -50,6 +50,7 @@
   // BootControlInterface overrides.
   unsigned int GetNumSlots() const override;
   BootControlInterface::Slot GetCurrentSlot() const override;
+  BootControlInterface::Slot GetFirstInactiveSlot() const override;
   bool GetPartitionDevice(const std::string& partition_name,
                           BootControlInterface::Slot slot,
                           bool not_in_payload,
@@ -73,6 +74,7 @@
 
  private:
   friend class BootControlChromeOSTest;
+  FRIEND_TEST(BootControlChromeOSTest, GetFirstInactiveSlot);
   FRIEND_TEST(BootControlChromeOSTest, SysfsBlockDeviceTest);
   FRIEND_TEST(BootControlChromeOSTest, GetPartitionNumberTest);
   FRIEND_TEST(BootControlChromeOSTest, ParseDlcPartitionNameTest);
diff --git a/cros/boot_control_chromeos_unittest.cc b/cros/boot_control_chromeos_unittest.cc
index 80a66f3..6387336 100644
--- a/cros/boot_control_chromeos_unittest.cc
+++ b/cros/boot_control_chromeos_unittest.cc
@@ -35,6 +35,13 @@
   BootControlChromeOS bootctl_;  // BootControlChromeOS under test.
 };
 
+TEST_F(BootControlChromeOSTest, GetFirstInactiveSlot) {
+  bootctl_.current_slot_ = 0;
+  EXPECT_EQ(1, bootctl_.GetFirstInactiveSlot());
+  bootctl_.current_slot_ = 1;
+  EXPECT_EQ(0, bootctl_.GetFirstInactiveSlot());
+}
+
 TEST_F(BootControlChromeOSTest, SysfsBlockDeviceTest) {
   EXPECT_EQ("/sys/block/sda", bootctl_.SysfsBlockDevice("/dev/sda"));
   EXPECT_EQ("", bootctl_.SysfsBlockDevice("/foo/sda"));
diff --git a/cros/common_service.cc b/cros/common_service.cc
index 4966c63..7247cbe 100644
--- a/cros/common_service.cc
+++ b/cros/common_service.cc
@@ -84,6 +84,14 @@
   return true;
 }
 
+bool UpdateEngineService::ApplyDeferredUpdate(ErrorPtr* error) {
+  if (!SystemState::Get()->update_attempter()->ApplyDeferredUpdate()) {
+    LogAndSetError(error, FROM_HERE, "Failed to apply deferred update.");
+    return false;
+  }
+  return true;
+}
+
 bool UpdateEngineService::AttemptInstall(brillo::ErrorPtr* error,
                                          const string& omaha_url,
                                          const vector<string>& dlc_ids) {
diff --git a/cros/common_service.h b/cros/common_service.h
index 228ceeb..6705357 100644
--- a/cros/common_service.h
+++ b/cros/common_service.h
@@ -45,6 +45,8 @@
               const update_engine::UpdateParams& update_params,
               bool* out_result);
 
+  bool ApplyDeferredUpdate(brillo::ErrorPtr* error);
+
   // Attempts a DLC module install operation.
   // |omaha_url|: the URL to query for update.
   // |dlc_ids|: a list of DLC module IDs.
diff --git a/cros/dbus_service.cc b/cros/dbus_service.cc
index 31baae9..b30d113 100644
--- a/cros/dbus_service.cc
+++ b/cros/dbus_service.cc
@@ -71,6 +71,10 @@
   return common_->Update(error, in_update_params, &result);
 }
 
+bool DBusUpdateEngineService::ApplyDeferredUpdate(ErrorPtr* error) {
+  return common_->ApplyDeferredUpdate(error);
+}
+
 bool DBusUpdateEngineService::AttemptInstall(ErrorPtr* error,
                                              const string& in_omaha_url,
                                              const vector<string>& dlc_ids) {
diff --git a/cros/dbus_service.h b/cros/dbus_service.h
index b040503..f7a7ffa 100644
--- a/cros/dbus_service.h
+++ b/cros/dbus_service.h
@@ -45,6 +45,8 @@
   bool Update(brillo::ErrorPtr* error,
               const update_engine::UpdateParams& in_update_params) override;
 
+  bool ApplyDeferredUpdate(brillo::ErrorPtr* error) override;
+
   bool AttemptInstall(brillo::ErrorPtr* error,
                       const std::string& in_omaha_url,
                       const std::vector<std::string>& dlc_ids) override;
diff --git a/cros/update_attempter.cc b/cros/update_attempter.cc
index 7eb8d56..215d95f 100644
--- a/cros/update_attempter.cc
+++ b/cros/update_attempter.cc
@@ -158,7 +158,10 @@
   // In case of update_engine restart without a reboot we need to restore the
   // reboot needed state.
   if (GetBootTimeAtUpdate(nullptr)) {
-    status_ = UpdateStatus::UPDATED_NEED_REBOOT;
+    if (prefs_->Exists(kPrefsDeferredUpdateCompleted))
+      status_ = UpdateStatus::UPDATED_BUT_DEFERRED;
+    else
+      status_ = UpdateStatus::UPDATED_NEED_REBOOT;
   } else {
     // Send metric before deleting prefs. Metric tells us how many times the
     // inactive partition was updated before the reboot.
@@ -316,6 +319,21 @@
     }
     LOG(INFO) << "Already updated but checking to see if there are more recent "
                  "updates available.";
+  } else if (status_ == UpdateStatus::UPDATED_BUT_DEFERRED) {
+    // Update is already deferred, don't proceed with repeated updates.
+    // Although we have applied an update, we still want to ping Omaha
+    // to ensure the number of active statistics is accurate.
+    //
+    // Also convey to the UpdateEngine.Check.Result metric that we're
+    // not performing an update check because of this.
+    LOG(INFO) << "Not updating b/c we deferred update, ping Omaha instead";
+    // TODO(kimjae): Add label for metric.
+    SystemState::Get()->metrics_reporter()->ReportUpdateCheckMetrics(
+        metrics::CheckResult::kDeferredUpdate,
+        metrics::CheckReaction::kUnset,
+        metrics::DownloadErrorCode::kUnset);
+    PingOmaha();
+    return;
   } else if (status_ != UpdateStatus::IDLE) {
     // Update in progress. Do nothing.
     return;
@@ -1005,6 +1023,53 @@
   return true;
 }
 
+bool UpdateAttempter::ApplyDeferredUpdate() {
+  if (status_ != UpdateStatus::UPDATED_BUT_DEFERRED) {
+    LOG(ERROR) << "Cannot apply deferred update when there isn't one "
+                  "deferred.";
+    return false;
+  }
+
+  LOG(INFO) << "Applying deferred update.";
+  install_plan_.reset(new InstallPlan());
+  auto* boot_control = SystemState::Get()->boot_control();
+
+  install_plan_->run_post_install = true;
+  install_plan_->defer_update_action = DeferUpdateAction::kApply;
+
+  // Since CrOS is A/B, it's okay to get the first inactive slot.
+  install_plan_->source_slot = boot_control->GetCurrentSlot();
+  install_plan_->target_slot = boot_control->GetFirstInactiveSlot();
+
+  install_plan_->partitions.push_back({
+      .name = "root",
+      .source_size = 1,
+      .target_size = 1,
+      .run_postinstall = true,
+      // TODO(kimjae): Store + override to handle non default script usage.
+      .postinstall_path = kPostinstallDefaultScript,
+  });
+  if (!install_plan_->LoadPartitionsFromSlots(boot_control)) {
+    LOG(ERROR) << "Failed to setup partitions for applying deferred update.";
+    return false;
+  }
+
+  install_plan_->Dump();
+
+  auto install_plan_action =
+      std::make_unique<InstallPlanAction>(*install_plan_);
+  auto postinstall_runner_action = std::make_unique<PostinstallRunnerAction>(
+      boot_control, SystemState::Get()->hardware());
+  postinstall_runner_action->set_delegate(this);
+  BondActions(install_plan_action.get(), postinstall_runner_action.get());
+  processor_->EnqueueAction(std::move(install_plan_action));
+  processor_->EnqueueAction(std::move(postinstall_runner_action));
+  processor_->set_delegate(this);
+
+  ScheduleProcessingStart();
+  return true;
+}
+
 bool UpdateAttempter::CheckForInstall(const vector<string>& dlc_ids,
                                       const string& omaha_url) {
   if (status_ != UpdateStatus::IDLE) {
@@ -1224,9 +1289,35 @@
 
   if (!SystemState::Get()->dlcservice()->UpdateCompleted(GetSuccessfulDlcIds()))
     LOG(WARNING) << "dlcservice didn't successfully handle update completion.";
-  SetStatusAndNotify(UpdateStatus::UPDATED_NEED_REBOOT);
-  ScheduleUpdates();
-  LOG(INFO) << "Update successfully applied, waiting to reboot.";
+
+  if (install_plan_) {
+    switch (install_plan_->defer_update_action) {
+      case DeferUpdateAction::kOff:
+        SetStatusAndNotify(UpdateStatus::UPDATED_NEED_REBOOT);
+        ScheduleUpdates();
+        LOG(INFO) << "Update successfully applied, waiting to reboot.";
+        break;
+      case DeferUpdateAction::kHold:
+        prefs_->SetString(kPrefsDeferredUpdateCompleted, "");
+        SetStatusAndNotify(UpdateStatus::UPDATED_BUT_DEFERRED);
+        ScheduleUpdates();
+        LOG(INFO) << "Deferred update hold action was successful.";
+        return;
+      case DeferUpdateAction::kApply:
+        SetStatusAndNotify(UpdateStatus::UPDATED_BUT_DEFERRED);
+        LOG(INFO) << "Deferred update apply action was successful, "
+                     "proceeding with reboot.";
+        if (!ResetStatus()) {
+          LOG(WARNING) << "Failed to reset status.";
+        }
+        RebootIfNeeded();
+        return;
+    }
+  } else {
+    SetStatusAndNotify(UpdateStatus::UPDATED_NEED_REBOOT);
+    ScheduleUpdates();
+    LOG(INFO) << "Update successfully applied, waiting to reboot.";
+  }
 
   // |install_plan_| is null during rollback operations, and the stats don't
   // make much sense then anyway.
@@ -1237,6 +1328,7 @@
     // Increment pref after every update.
     SystemState::Get()->prefs()->SetInt64(kPrefsConsecutiveUpdateCount,
                                           ++num_consecutive_updates);
+    // TODO(kimjae): Seperate out apps into categories (OS, DLC, etc).
     // Generate an unique payload identifier.
     string target_version_uid;
     for (const auto& payload : install_plan_->payloads) {
@@ -1400,6 +1492,7 @@
         case UpdateStatus::ATTEMPTING_ROLLBACK:
         case UpdateStatus::DISABLED:
         case UpdateStatus::CLEANUP_PREVIOUS_UPDATE:
+        case UpdateStatus::UPDATED_BUT_DEFERRED:
           MarkDeltaUpdateFailure();
           // Errored out after partition was marked unbootable.
           int64_t num_consecutive_updates = 0;
@@ -1461,7 +1554,11 @@
     LOG(INFO)
         << "Cancelling current update but going back to need reboot as there "
            "is an update in the inactive partition that can be applied.";
-    SetStatusAndNotify(UpdateStatus::UPDATED_NEED_REBOOT);
+    if (prefs_->Exists(kPrefsDeferredUpdateCompleted)) {
+      SetStatusAndNotify(UpdateStatus::UPDATED_BUT_DEFERRED);
+    } else {
+      SetStatusAndNotify(UpdateStatus::UPDATED_NEED_REBOOT);
+    }
     return;
   }
   // One full update never completed or there no longer an inactive partition
@@ -1476,6 +1573,7 @@
   ret_value = prefs->Delete(kPrefsUpdateCompletedBootTime) && ret_value;
   ret_value = prefs->Delete(kPrefsLastFp, {kDlcPrefsSubDir}) && ret_value;
   ret_value = prefs->Delete(kPrefsPreviousVersion) && ret_value;
+  ret_value = prefs->Delete(kPrefsDeferredUpdateCompleted) && ret_value;
   return ret_value;
 }
 
@@ -1579,7 +1677,23 @@
       LOG(INFO) << "Reset status " << (ret_value ? "successful" : "failed");
       return ret_value;
     }
+    case UpdateStatus::UPDATED_BUT_DEFERRED: {
+      bool ret_value = true;
+      status_ = UpdateStatus::IDLE;
+      ret_value = ResetUpdatePrefs() && ret_value;
 
+      // Notify the PayloadState that the successful payload was canceled.
+      SystemState::Get()->payload_state()->ResetUpdateStatus();
+
+      // The previous version is used to report back to omaha after reboot that
+      // we actually rebooted into the new version from this "prev-version". We
+      // need to clear out this value now to prevent it being sent on the next
+      // updatecheck request.
+      ret_value = prefs_->SetString(kPrefsPreviousVersion, "") && ret_value;
+
+      LOG(INFO) << "Reset status " << (ret_value ? "successful" : "failed");
+      return ret_value;
+    }
     default:
       LOG(ERROR) << "Reset not allowed in this state.";
       return false;
@@ -1753,6 +1867,11 @@
         /*success=*/false, install_plan_->version);
   }
 
+  if (install_plan_ &&
+      install_plan_->defer_update_action == DeferUpdateAction::kApply) {
+    // TODO(kimjae): Report deferred update apply action failure metric.
+  }
+
   // Send it to Omaha.
   LOG(INFO) << "Reporting the error event";
   auto error_event_action = std::make_unique<OmahaRequestAction>(
@@ -1829,7 +1948,11 @@
   UpdateLastCheckedTime();
 
   // Update the status which will schedule the next update check
-  SetStatusAndNotify(UpdateStatus::UPDATED_NEED_REBOOT);
+  if (prefs_->Exists(kPrefsDeferredUpdateCompleted)) {
+    SetStatusAndNotify(UpdateStatus::UPDATED_BUT_DEFERRED);
+  } else {
+    SetStatusAndNotify(UpdateStatus::UPDATED_NEED_REBOOT);
+  }
   ScheduleUpdates();
 }
 
@@ -1965,11 +2088,14 @@
   string boot_id;
   TEST_AND_RETURN_FALSE(utils::GetBootId(&boot_id));
 
+  // Reboots are allowed when updates get deferred, since they are actually
+  // applied just not active. Hence the check on `kPrefsDeferredUpdate`.
   string update_completed_on_boot_id;
-  if (!prefs_->Exists(kPrefsUpdateCompletedOnBootId) ||
-      !prefs_->GetString(kPrefsUpdateCompletedOnBootId,
-                         &update_completed_on_boot_id) ||
-      update_completed_on_boot_id != boot_id)
+  if (!prefs_->Exists(kPrefsDeferredUpdateCompleted) &&
+      (!prefs_->Exists(kPrefsUpdateCompletedOnBootId) ||
+       !prefs_->GetString(kPrefsUpdateCompletedOnBootId,
+                          &update_completed_on_boot_id) ||
+       update_completed_on_boot_id != boot_id))
     return false;
 
   // Short-circuit avoiding the read in case out_boot_time is nullptr.
diff --git a/cros/update_attempter.h b/cros/update_attempter.h
index 8c8dd3d..815184f 100644
--- a/cros/update_attempter.h
+++ b/cros/update_attempter.h
@@ -142,6 +142,10 @@
   // update was already in progress.
   virtual bool CheckForUpdate(const update_engine::UpdateParams& update_params);
 
+  // This is the internal entry point to apply a deferred update, will return
+  // false if there wasn't a deferred update to apply or on failure.
+  virtual bool ApplyDeferredUpdate();
+
   // This is the version of CheckForUpdate called by AttemptInstall API.
   virtual bool CheckForInstall(const std::vector<std::string>& dlc_ids,
                                const std::string& omaha_url);
diff --git a/cros/update_attempter_unittest.cc b/cros/update_attempter_unittest.cc
index a82943f..aa5e57f 100644
--- a/cros/update_attempter_unittest.cc
+++ b/cros/update_attempter_unittest.cc
@@ -107,6 +107,7 @@
     UpdateStatus::ATTEMPTING_ROLLBACK,
     UpdateStatus::DISABLED,
     UpdateStatus::NEED_PERMISSION_TO_UPDATE,
+    UpdateStatus::UPDATED_BUT_DEFERRED,
 };
 
 struct CheckForUpdateTestParams {
@@ -2704,9 +2705,11 @@
   auto* fake_prefs = FakeSystemState::Get()->prefs();
   fake_prefs->SetString(kPrefsLastFp, "3.14");
   fake_prefs->SetString(kPrefsPreviousVersion, "prev-version");
+  fake_prefs->SetString(kPrefsDeferredUpdateCompleted, "");
 
   // Make sure prefs are deleted.
   EXPECT_TRUE(attempter_.ResetUpdatePrefs());
+  EXPECT_FALSE(fake_prefs->Exists(kPrefsDeferredUpdateCompleted));
   EXPECT_FALSE(fake_prefs->Exists(kPrefsUpdateCompletedOnBootId));
   EXPECT_FALSE(fake_prefs->Exists(kPrefsUpdateCompletedBootTime));
   EXPECT_FALSE(fake_prefs->Exists(kPrefsLastFp));
diff --git a/cros/update_engine_client.cc b/cros/update_engine_client.cc
index f3a2dc2..b95d438 100644
--- a/cros/update_engine_client.cc
+++ b/cros/update_engine_client.cc
@@ -226,6 +226,9 @@
                 "target channel is more stable than the current channel unless "
                 "--nopowerwash is specified.");
   DEFINE_bool(check_for_update, false, "Initiate check for updates.");
+  DEFINE_bool(apply_deferred_update,
+              false,
+              "Apply the deferred update if there is one.");
   DEFINE_string(
       cohort_hint, "", "Set the current cohort hint to the passed value.");
   DEFINE_bool(follow,
@@ -484,6 +487,14 @@
       LOG(INFO) << "Target Channel (pending update): " << target_channel;
   }
 
+  if (FLAGS_apply_deferred_update) {
+    if (!client_->ApplyDeferredUpdate()) {
+      LOG(ERROR) << "Apply deferred update failed.";
+      return 1;
+    }
+    return 0;
+  }
+
   bool do_update_request = FLAGS_check_for_update || FLAGS_update ||
                            !FLAGS_app_version.empty() ||
                            !FLAGS_omaha_url.empty();
diff --git a/dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml b/dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml
index 72f4486..ab5b834 100644
--- a/dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml
+++ b/dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml
@@ -29,6 +29,8 @@
                     value="update_engine::UpdateParams"/>
       </arg>
     </method>
+    <method name="ApplyDeferredUpdate">
+    </method>
     <method name="AttemptInstall">
       <arg type="s" name="omaha_url" direction="in" />
       <arg type="as" name="dlc_ids" direction="in">
diff --git a/payload_consumer/install_plan.h b/payload_consumer/install_plan.h
index 1eea739..abfb03d 100644
--- a/payload_consumer/install_plan.h
+++ b/payload_consumer/install_plan.h
@@ -38,6 +38,12 @@
 
 std::string InstallPayloadTypeToString(InstallPayloadType type);
 
+enum class DeferUpdateAction {
+  kOff,
+  kHold,
+  kApply,
+};
+
 struct InstallPlan {
   InstallPlan() = default;
 
@@ -182,6 +188,9 @@
   // Indicates the type of update.
   update_engine::UpdateUrgencyInternal update_urgency{
       update_engine::UpdateUrgencyInternal::REGULAR};
+
+  // The defer update action to perform during post installation.
+  DeferUpdateAction defer_update_action{DeferUpdateAction::kOff};
 };
 
 class InstallPlanAction;
diff --git a/payload_consumer/postinstall_runner_action.cc b/payload_consumer/postinstall_runner_action.cc
index 40302e5..1b3c87b 100644
--- a/payload_consumer/postinstall_runner_action.cc
+++ b/payload_consumer/postinstall_runner_action.cc
@@ -95,9 +95,16 @@
 }
 
 void PostinstallRunnerAction::PerformPartitionPostinstall() {
-  if (install_plan_.download_url.empty()) {
-    LOG(INFO) << "Skipping post-install during rollback";
-    return CompletePostinstall(ErrorCode::kSuccess);
+  switch (install_plan_.defer_update_action) {
+    case DeferUpdateAction::kOff:
+      if (install_plan_.download_url.empty()) {
+        LOG(INFO) << "Skipping post-install during rollback";
+        return CompletePostinstall(ErrorCode::kSuccess);
+      }
+      break;
+    case DeferUpdateAction::kHold:
+    case DeferUpdateAction::kApply:
+      break;
   }
 
   // Skip all the partitions that don't have a post-install step.
@@ -175,6 +182,20 @@
   // Chrome OS postinstall expects the target rootfs as the first parameter.
   command.push_back(partition.target_path);
 
+  // Defer update action to apply.
+  switch (install_plan_.defer_update_action) {
+    case DeferUpdateAction::kOff:
+      break;
+    case DeferUpdateAction::kHold:
+      LOG(INFO) << "Defer update action: hold";
+      command.push_back("--defer_update_action=hold");
+      break;
+    case DeferUpdateAction::kApply:
+      LOG(INFO) << "Defer update action: apply";
+      command.push_back("--defer_update_action=apply");
+      break;
+  }
+
   current_command_ = Subprocess::Get().ExecFlags(
       command,
       Subprocess::kRedirectStderrToStdout,
@@ -332,7 +353,15 @@
         hardware_->SetWarmReset(true);
       }
     } else if (install_plan_.run_post_install) {
-      error_code = ErrorCode::kUpdatedButNotActive;
+      switch (install_plan_.defer_update_action) {
+        case DeferUpdateAction::kOff:
+          error_code = ErrorCode::kUpdatedButNotActive;
+          break;
+        case DeferUpdateAction::kHold:
+        case DeferUpdateAction::kApply:
+          error_code = ErrorCode::kSuccess;
+          break;
+      }
     }
   }
 
diff --git a/update_manager/consumer_auto_update_policy_impl.cc b/update_manager/consumer_auto_update_policy_impl.cc
deleted file mode 100644
index 04c2c48..0000000
--- a/update_manager/consumer_auto_update_policy_impl.cc
+++ /dev/null
@@ -1,73 +0,0 @@
-//
-// Copyright (C) 2021 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-#include "update_engine/update_manager/consumer_auto_update_policy_impl.h"
-
-#include "update_engine/common/constants.h"
-#include "update_engine/common/system_state.h"
-#include "update_engine/update_manager/update_check_allowed_policy_data.h"
-
-#include <base/logging.h>
-
-namespace chromeos_update_manager {
-
-// Do not perform any updates if consumer has disabled auto updates.
-// However allow interactive updates to continue.
-EvalStatus ConsumerAutoUpdatePolicyImpl::Evaluate(
-    EvaluationContext* ec,
-    State* state,
-    std::string* error,
-    PolicyDataInterface* data) const {
-  // TODO(crbug.com/1278079): Check for update but skip applying when consumer
-  // update is disabled. This will require adding fields to UpdateCheckParams.
-  DevicePolicyProvider* const dp_provider = state->device_policy_provider();
-  UpdateCheckParams* result =
-      UpdateCheckAllowedPolicyData::GetUpdateCheckParams(data);
-
-  // Skip check if device is managed.
-  const bool* has_owner_p = ec->GetValue(dp_provider->var_has_owner());
-  if (has_owner_p && !(*has_owner_p)) {
-    LOG(INFO) << "Managed device, ignoring consumer auto update.";
-    return EvalStatus::kContinue;
-  }
-
-  // Otherwise, check if the consumer device has auto updates disabled.
-  const bool* updater_consumer_auto_update_disabled_p = ec->GetValue(
-      state->updater_provider()->var_consumer_auto_update_disabled());
-  if (updater_consumer_auto_update_disabled_p) {
-    // Auto update is enabled.
-    if (!(*updater_consumer_auto_update_disabled_p)) {
-      LOG(INFO) << "Consumer auto update is enabled.";
-      return EvalStatus::kContinue;
-    }
-
-    // Auto update is disabled.
-
-    // If interactive, ignore the disabled consumer auto update.
-    // This is a safety check.
-    if (!result->interactive) {
-      LOG(INFO) << "Disabled consumer auto update.";
-      return EvalStatus::kAskMeAgainLater;
-    }
-    LOG(INFO) << "Disabled consumer auto update, "
-              << "but continuing as interactive.";
-  }
-
-  LOG(WARNING) << "Couldn't find consumer auto update value.";
-  return EvalStatus::kContinue;
-}
-
-}  // namespace chromeos_update_manager
diff --git a/update_manager/consumer_auto_update_policy_impl.h b/update_manager/consumer_auto_update_policy_impl.h
deleted file mode 100644
index 3bc06ac..0000000
--- a/update_manager/consumer_auto_update_policy_impl.h
+++ /dev/null
@@ -1,48 +0,0 @@
-//
-// Copyright (C) 2021 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-#ifndef UPDATE_ENGINE_UPDATE_MANAGER_CONSUMER_AUTO_UPDATE_POLICY_IMPL_H_
-#define UPDATE_ENGINE_UPDATE_MANAGER_CONSUMER_AUTO_UPDATE_POLICY_IMPL_H_
-
-#include <string>
-
-#include "update_engine/update_manager/policy_interface.h"
-
-namespace chromeos_update_manager {
-
-class ConsumerAutoUpdatePolicyImpl : public PolicyInterface {
- public:
-  ConsumerAutoUpdatePolicyImpl() = default;
-  ConsumerAutoUpdatePolicyImpl(const ConsumerAutoUpdatePolicyImpl&) = delete;
-  ConsumerAutoUpdatePolicyImpl& operator=(const ConsumerAutoUpdatePolicyImpl&) =
-      delete;
-
-  ~ConsumerAutoUpdatePolicyImpl() override = default;
-
-  std::string PolicyName() const override {
-    return "ConsumerAutoUpdatePolicyImpl";
-  }
-
-  // Policy overrides.
-  EvalStatus Evaluate(EvaluationContext* ec,
-                      State* state,
-                      std::string* error,
-                      PolicyDataInterface* data) const override;
-};
-
-}  // namespace chromeos_update_manager
-
-#endif  // UPDATE_ENGINE_UPDATE_MANAGER_CONSUMER_AUTO_UPDATE_POLICY_IMPL_H_
diff --git a/update_manager/consumer_auto_update_policy_impl_unittest.cc b/update_manager/consumer_auto_update_policy_impl_unittest.cc
deleted file mode 100644
index 5fa159e..0000000
--- a/update_manager/consumer_auto_update_policy_impl_unittest.cc
+++ /dev/null
@@ -1,87 +0,0 @@
-//
-// Copyright 2021 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-#include "update_engine/cros/fake_system_state.h"
-#include "update_engine/update_manager/consumer_auto_update_policy_impl.h"
-#include "update_engine/update_manager/policy_test_utils.h"
-
-using chromeos_update_engine::FakeSystemState;
-using testing::_;
-using testing::Return;
-
-namespace chromeos_update_manager {
-
-class UmConsumerAutoUpdatePolicyImplTest : public UmPolicyTestBase {
- protected:
-  UmConsumerAutoUpdatePolicyImplTest() : UmPolicyTestBase() {
-    policy_data_.reset(new UpdateCheckAllowedPolicyData());
-    policy_2_.reset(new ConsumerAutoUpdatePolicyImpl());
-
-    ucp_ =
-        UpdateCheckAllowedPolicyData::GetUpdateCheckParams(policy_data_.get());
-  }
-
-  void SetUp() override {
-    UmPolicyTestBase::SetUp();
-    FakeSystemState::CreateInstance();
-    FakeSystemState::Get()->set_prefs(nullptr);
-  }
-
-  UpdateCheckParams* ucp_;
-};
-
-TEST_F(UmConsumerAutoUpdatePolicyImplTest, SkipIfDevicePolicyExists) {
-  fake_state_.device_policy_provider()->var_device_policy_is_loaded()->reset(
-      new bool(true));
-  EXPECT_EQ(EvalStatus::kContinue, evaluator_->Evaluate());
-}
-
-TEST_F(UmConsumerAutoUpdatePolicyImplTest, SkipIfNotDisabled) {
-  fake_state_.device_policy_provider()->var_has_owner()->reset(new bool(false));
-  EXPECT_EQ(EvalStatus::kContinue, evaluator_->Evaluate());
-}
-
-TEST_F(UmConsumerAutoUpdatePolicyImplTest, ConsumerDeviceEnabledAutoUpdate) {
-  fake_state_.device_policy_provider()->var_has_owner()->reset(new bool(true));
-  fake_state_.updater_provider()->var_consumer_auto_update_disabled()->reset(
-      new bool(false));
-  EXPECT_EQ(EvalStatus::kContinue, evaluator_->Evaluate());
-}
-
-TEST_F(UmConsumerAutoUpdatePolicyImplTest,
-       ConsumerDeviceDisabledAutoUpdateBackgroundCheck) {
-  fake_state_.device_policy_provider()->var_has_owner()->reset(new bool(true));
-  fake_state_.updater_provider()->var_consumer_auto_update_disabled()->reset(
-      new bool(true));
-  ucp_->interactive = false;
-  EXPECT_EQ(EvalStatus::kAskMeAgainLater, evaluator_->Evaluate());
-}
-
-TEST_F(UmConsumerAutoUpdatePolicyImplTest,
-       ConsumerDeviceDisabledAutoUpdateInteractiveCheck) {
-  fake_state_.device_policy_provider()->var_has_owner()->reset(new bool(true));
-  fake_state_.updater_provider()->var_consumer_auto_update_disabled()->reset(
-      new bool(false));
-  ucp_->interactive = true;
-  EXPECT_EQ(EvalStatus::kContinue, evaluator_->Evaluate());
-}
-
-TEST_F(UmConsumerAutoUpdatePolicyImplTest, ManagedDeviceContinues) {
-  fake_state_.device_policy_provider()->var_has_owner()->reset(new bool(false));
-  EXPECT_EQ(EvalStatus::kContinue, evaluator_->Evaluate());
-}
-
-}  // namespace chromeos_update_manager
diff --git a/update_manager/deferred_update_policy_impl.cc b/update_manager/deferred_update_policy_impl.cc
new file mode 100644
index 0000000..6d962a3
--- /dev/null
+++ b/update_manager/deferred_update_policy_impl.cc
@@ -0,0 +1,74 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/update_manager/deferred_update_policy_impl.h"
+
+#include "update_engine/common/constants.h"
+#include "update_engine/common/system_state.h"
+#include "update_engine/update_manager/update_can_be_applied_policy_data.h"
+
+#include <base/logging.h>
+
+using chromeos_update_engine::DeferUpdateAction;
+
+namespace chromeos_update_manager {
+
+// Defer updates if consumer has disabled auto updates.
+EvalStatus DeferredUpdatePolicyImpl::Evaluate(EvaluationContext* ec,
+                                              State* state,
+                                              std::string* error,
+                                              PolicyDataInterface* data) const {
+  DevicePolicyProvider* const dp_provider = state->device_policy_provider();
+
+  auto* policy_data = static_cast<UpdateCanBeAppliedPolicyData*>(data);
+  auto* install_plan = policy_data->install_plan();
+
+  // Althought the default for `defer_update_action` is `kOff`, explicitly set
+  // in order to not cause potential accidental overrides/defaults missing.
+  install_plan->defer_update_action = DeferUpdateAction::kOff;
+
+  // Skip check if device is managed.
+  const bool* has_owner_p = ec->GetValue(dp_provider->var_has_owner());
+  if (has_owner_p && !(*has_owner_p)) {
+    LOG(INFO) << "Managed device, not deferring updates.";
+    return EvalStatus::kContinue;
+  }
+
+  // Otherwise, check if the consumer device has auto updates disabled.
+  const bool* updater_consumer_auto_update_disabled_p = ec->GetValue(
+      state->updater_provider()->var_consumer_auto_update_disabled());
+  if (updater_consumer_auto_update_disabled_p) {
+    // Consumer auto update is enabled.
+    if (!(*updater_consumer_auto_update_disabled_p)) {
+      LOG(INFO) << "Consumer auto update is enabled, not deferring updates.";
+      return EvalStatus::kContinue;
+    }
+
+    // Consumer auto update is disabled.
+    LOG(INFO) << "Consumer auto update is disabled, deferring updates.";
+    install_plan->defer_update_action = DeferUpdateAction::kHold;
+    // The installer (postinstall) script will hold back the partition table
+    // update, so we must do the same from the autoupdater side.
+    install_plan->switch_slot_on_reboot = false;
+    return EvalStatus::kContinue;
+  }
+
+  LOG(WARNING)
+      << "Couldn't find consumer auto update value, not deferring updates.";
+  return EvalStatus::kContinue;
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/deferred_update_policy_impl.h b/update_manager/deferred_update_policy_impl.h
new file mode 100644
index 0000000..7a90c4d
--- /dev/null
+++ b/update_manager/deferred_update_policy_impl.h
@@ -0,0 +1,45 @@
+//
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_UPDATE_MANAGER_DEFERRED_UPDATE_POLICY_IMPL_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_DEFERRED_UPDATE_POLICY_IMPL_H_
+
+#include <string>
+
+#include "update_engine/update_manager/policy_interface.h"
+
+namespace chromeos_update_manager {
+
+class DeferredUpdatePolicyImpl : public PolicyInterface {
+ public:
+  DeferredUpdatePolicyImpl() = default;
+  DeferredUpdatePolicyImpl(const DeferredUpdatePolicyImpl&) = delete;
+  DeferredUpdatePolicyImpl& operator=(const DeferredUpdatePolicyImpl&) = delete;
+
+  ~DeferredUpdatePolicyImpl() override = default;
+
+  std::string PolicyName() const override { return "DeferredUpdatePolicyImpl"; }
+
+  // Policy overrides.
+  EvalStatus Evaluate(EvaluationContext* ec,
+                      State* state,
+                      std::string* error,
+                      PolicyDataInterface* data) const override;
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_DEFERRED_UPDATE_POLICY_IMPL_H_
diff --git a/update_manager/deferred_update_policy_impl_unittest.cc b/update_manager/deferred_update_policy_impl_unittest.cc
new file mode 100644
index 0000000..3f07414
--- /dev/null
+++ b/update_manager/deferred_update_policy_impl_unittest.cc
@@ -0,0 +1,82 @@
+//
+// Copyright 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/cros/fake_system_state.h"
+#include "update_engine/payload_consumer/install_plan.h"
+#include "update_engine/update_manager/deferred_update_policy_impl.h"
+#include "update_engine/update_manager/policy_test_utils.h"
+#include "update_engine/update_manager/update_can_be_applied_policy_data.h"
+
+using chromeos_update_engine::DeferUpdateAction;
+using chromeos_update_engine::FakeSystemState;
+using chromeos_update_engine::InstallPlan;
+using testing::_;
+using testing::Return;
+
+namespace chromeos_update_manager {
+
+class UmDeferredUpdatePolicyImplTest : public UmPolicyTestBase {
+ protected:
+  UmDeferredUpdatePolicyImplTest() : UmPolicyTestBase() {
+    policy_data_.reset(new UpdateCanBeAppliedPolicyData(&install_plan_));
+    policy_2_.reset(new DeferredUpdatePolicyImpl());
+  }
+
+  void SetUp() override {
+    UmPolicyTestBase::SetUp();
+    FakeSystemState::CreateInstance();
+    FakeSystemState::Get()->set_prefs(nullptr);
+  }
+
+  InstallPlan install_plan_;
+};
+
+TEST_F(UmDeferredUpdatePolicyImplTest, SkipIfDevicePolicyExists) {
+  fake_state_.device_policy_provider()->var_device_policy_is_loaded()->reset(
+      new bool(true));
+  EXPECT_EQ(EvalStatus::kContinue, evaluator_->Evaluate());
+  EXPECT_EQ(DeferUpdateAction::kOff, install_plan_.defer_update_action);
+}
+
+TEST_F(UmDeferredUpdatePolicyImplTest, SkipIfNotDisabled) {
+  fake_state_.device_policy_provider()->var_has_owner()->reset(new bool(false));
+  EXPECT_EQ(EvalStatus::kContinue, evaluator_->Evaluate());
+  EXPECT_EQ(DeferUpdateAction::kOff, install_plan_.defer_update_action);
+}
+
+TEST_F(UmDeferredUpdatePolicyImplTest, ConsumerDeviceEnabledAutoUpdate) {
+  fake_state_.device_policy_provider()->var_has_owner()->reset(new bool(true));
+  fake_state_.updater_provider()->var_consumer_auto_update_disabled()->reset(
+      new bool(false));
+  EXPECT_EQ(EvalStatus::kContinue, evaluator_->Evaluate());
+  EXPECT_EQ(DeferUpdateAction::kOff, install_plan_.defer_update_action);
+}
+
+TEST_F(UmDeferredUpdatePolicyImplTest, ConsumerDeviceDisabledAutoUpdate) {
+  fake_state_.device_policy_provider()->var_has_owner()->reset(new bool(true));
+  fake_state_.updater_provider()->var_consumer_auto_update_disabled()->reset(
+      new bool(true));
+  EXPECT_EQ(EvalStatus::kContinue, evaluator_->Evaluate());
+  EXPECT_EQ(DeferUpdateAction::kHold, install_plan_.defer_update_action);
+}
+
+TEST_F(UmDeferredUpdatePolicyImplTest, ManagedDeviceContinues) {
+  fake_state_.device_policy_provider()->var_has_owner()->reset(new bool(false));
+  EXPECT_EQ(EvalStatus::kContinue, evaluator_->Evaluate());
+  EXPECT_EQ(DeferUpdateAction::kOff, install_plan_.defer_update_action);
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/update_can_be_applied_policy.cc b/update_manager/update_can_be_applied_policy.cc
index 89d1fa0..16321be 100644
--- a/update_manager/update_can_be_applied_policy.cc
+++ b/update_manager/update_can_be_applied_policy.cc
@@ -22,6 +22,7 @@
 #include <base/logging.h>
 
 #include "update_engine/common/error_code.h"
+#include "update_engine/update_manager/deferred_update_policy_impl.h"
 #include "update_engine/update_manager/enterprise_rollback_policy_impl.h"
 #include "update_engine/update_manager/interactive_update_policy_impl.h"
 #include "update_engine/update_manager/minimum_version_policy_impl.h"
@@ -43,6 +44,7 @@
   EnterpriseRollbackPolicyImpl enterprise_rollback_policy;
   MinimumVersionPolicyImpl minimum_version_policy;
   UpdateTimeRestrictionsPolicyImpl update_time_restrictions_policy;
+  DeferredUpdatePolicyImpl deferred_update_policy;
 
   vector<PolicyInterface const*> policies_to_consult = {
       // Check to see if an interactive update has been requested.
@@ -58,6 +60,10 @@
       // Do not apply or download an update if we are inside one of the
       // restricted times.
       &update_time_restrictions_policy,
+
+      // Check to see if deferred updates is required.
+      // Note: Always run later than interactive policy check.
+      &deferred_update_policy,
   };
 
   for (auto policy : policies_to_consult) {
diff --git a/update_manager/update_check_allowed_policy.cc b/update_manager/update_check_allowed_policy.cc
index 994e6ba..605343d 100644
--- a/update_manager/update_check_allowed_policy.cc
+++ b/update_manager/update_check_allowed_policy.cc
@@ -21,7 +21,6 @@
 #include <base/strings/string_util.h>
 
 #include "update_engine/common/system_state.h"
-#include "update_engine/update_manager/consumer_auto_update_policy_impl.h"
 #include "update_engine/update_manager/enough_slots_ab_updates_policy_impl.h"
 #include "update_engine/update_manager/enterprise_device_policy_impl.h"
 #include "update_engine/update_manager/interactive_update_policy_impl.h"
@@ -69,7 +68,6 @@
   OnlyUpdateOfficialBuildsPolicyImpl only_update_official_builds_policy;
   InteractiveUpdateCheckAllowedPolicyImpl interactive_update_policy;
   OobePolicyImpl oobe_policy;
-  ConsumerAutoUpdatePolicyImpl consumer_auto_update_policy;
   NextUpdateCheckTimePolicyImpl next_update_check_time_policy;
 
   vector<PolicyInterface* const> policies_to_consult = {
@@ -87,11 +85,6 @@
       // Check to see if an interactive update was requested.
       &interactive_update_policy,
 
-      // Check to see if consumer auto updates are allowed.
-      // Note: Depends on interactive update policy to coninue, so must run
-      // after that policy evaluation.
-      &consumer_auto_update_policy,
-
       // Unofficial builds should not perform periodic update checks.
       &only_update_official_builds_policy,
 
diff --git a/update_status_utils.cc b/update_status_utils.cc
index fdfdd4c..6ed53b3 100644
--- a/update_status_utils.cc
+++ b/update_status_utils.cc
@@ -75,6 +75,8 @@
       return update_engine::kUpdateStatusDisabled;
     case UpdateStatus::CLEANUP_PREVIOUS_UPDATE:
       return update_engine::kUpdateStatusCleanupPreviousUpdate;
+    case UpdateStatus::UPDATED_BUT_DEFERRED:
+      return update_engine::kUpdateStatusUpdatedButDeferred;
   }
 
   NOTREACHED();