Merge remote-tracking branch 'aosp/upstream-master' into aosp/master.

The following change is reverted because aosp has newer libchrome.
71818c84 Partially Revert 2b9d241

Added stub override for ReportInternalErrorCode().
Fixed RunPosinstallAction typo.

Bug: 112326236
Test: update_engine_unittests
Change-Id: Ieaae0eef425cbb1278067a48aa19b14ed056317a
diff --git a/.clang-format b/.clang-format
deleted file mode 120000
index f412743..0000000
--- a/.clang-format
+++ /dev/null
@@ -1 +0,0 @@
-../../build/tools/brillo-clang-format
\ No newline at end of file
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..c1244fe
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,38 @@
+#
+# Copyright (C) 2016 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.
+#
+#
+# This is the .clang-format file used by all Brillo projects, conforming to the
+# style guide defined by Brillo. To use this file create a *relative* symlink in
+# your project pointing to this file, as this repository is expected to be
+# present in all manifests.
+#
+# See go/brillo-c++-style for details about the style guide.
+#
+
+# WARN: We do not symlink this file to the original file because their location
+# are different in AOSP and CrOS. Keep in sync with the original file if
+# possible.
+
+BasedOnStyle: Google
+AllowShortFunctionsOnASingleLine: Inline
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+BinPackArguments: false
+BinPackParameters: false
+CommentPragmas: NOLINT:.*
+DerivePointerAlignment: false
+PointerAlignment: Left
+TabWidth: 2
diff --git a/Android.mk b/Android.mk
index 171eb0e..08492c0 100644
--- a/Android.mk
+++ b/Android.mk
@@ -318,6 +318,7 @@
     proxy_resolver.cc \
     real_system_state.cc \
     update_attempter.cc \
+    update_boot_flags_action.cc \
     update_manager/android_things_policy.cc \
     update_manager/api_restricted_downloads_policy_impl.cc \
     update_manager/boxed_value.cc \
@@ -334,8 +335,11 @@
     update_manager/real_system_provider.cc \
     update_manager/real_time_provider.cc \
     update_manager/real_updater_provider.cc \
+    update_manager/staging_utils.cc \
     update_manager/state_factory.cc \
     update_manager/update_manager.cc \
+    update_manager/update_time_restrictions_policy_impl.cc \
+    update_manager/weekly_time.cc \
     update_status_utils.cc \
     utils_android.cc
 ifeq ($(local_use_binder),1)
@@ -418,6 +422,7 @@
     network_selector_android.cc \
     proxy_resolver.cc \
     update_attempter_android.cc \
+    update_boot_flags_action.cc \
     update_status_utils.cc \
     utils_android.cc
 include $(BUILD_STATIC_LIBRARY)
@@ -494,6 +499,7 @@
     proxy_resolver.cc \
     sideload_main.cc \
     update_attempter_android.cc \
+    update_boot_flags_action.cc \
     update_status_utils.cc \
     utils_android.cc
 LOCAL_STATIC_LIBRARIES := \
@@ -1001,6 +1007,7 @@
     payload_state_unittest.cc \
     parcelable_update_engine_status_unittest.cc \
     update_attempter_unittest.cc \
+    update_boot_flags_action_unittest.cc \
     update_manager/android_things_policy_unittest.cc \
     update_manager/boxed_value_unittest.cc \
     update_manager/chromeos_policy.cc \
@@ -1017,9 +1024,12 @@
     update_manager/real_system_provider_unittest.cc \
     update_manager/real_time_provider_unittest.cc \
     update_manager/real_updater_provider_unittest.cc \
+    update_manager/staging_utils_unittest.cc \
     update_manager/umtest_utils.cc \
     update_manager/update_manager_unittest.cc \
-    update_manager/variable_unittest.cc
+    update_manager/update_time_restrictions_policy_impl_unittest.cc \
+    update_manager/variable_unittest.cc \
+    update_manager/weekly_time_unittest.cc
 else  # local_use_omaha == 1
 LOCAL_STATIC_LIBRARIES += \
     libupdate_engine_android \
diff --git a/CPPLINT.cfg b/CPPLINT.cfg
index 259fb2f..f7dde21 100644
--- a/CPPLINT.cfg
+++ b/CPPLINT.cfg
@@ -1,7 +1,4 @@
 # This should be kept in sync with platform2/CPPLINT.cfg
 set noparent
 
-# Header guard should start with UPDATE_ENGINE_
-root=..
-
-filter=-build/include_order,+build/include_alpha
+filter=-build/include_order,+build/include_alpha,-build/header_guard
diff --git a/PRESUBMIT.cfg b/PRESUBMIT.cfg
index 3b8b271..f2c7831 100644
--- a/PRESUBMIT.cfg
+++ b/PRESUBMIT.cfg
@@ -3,5 +3,6 @@
 hook1=../../../platform2/common-mk/gyplint.py ${PRESUBMIT_FILES}
 
 [Hook Overrides]
+clang_format_check: true
 cros_license_check: false
 aosp_license_check: true
diff --git a/UpdateEngine.conf b/UpdateEngine.conf
index 58cca09..192e6ab 100644
--- a/UpdateEngine.conf
+++ b/UpdateEngine.conf
@@ -53,6 +53,9 @@
            send_member="SetUpdateOverCellularPermission"/>
     <allow send_destination="org.chromium.UpdateEngine"
            send_interface="org.chromium.UpdateEngineInterface"
+           send_member="SetUpdateOverCellularTarget"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
            send_member="GetUpdateOverCellularPermission"/>
     <allow send_destination="org.chromium.UpdateEngine"
            send_interface="org.chromium.UpdateEngineInterface"
diff --git a/WATCHLISTS b/WATCHLISTS
deleted file mode 100644
index bcce0de..0000000
--- a/WATCHLISTS
+++ /dev/null
@@ -1,14 +0,0 @@
-# See http://dev.chromium.org/developers/contributing-code/watchlists for
-# a description of this file's format.
-# Please keep these keys in alphabetical order.
-
-{
-  'WATCHLIST_DEFINITIONS': {
-    'all': {
-      'filepath': '.',
-    },
-  },
-  'WATCHLISTS': {
-    'all': ['adlr@chromium.org', 'petkov@chromium.org']
-  },
-}
diff --git a/binder_bindings/android/brillo/IUpdateEngine.aidl b/binder_bindings/android/brillo/IUpdateEngine.aidl
index e549a4d..56e1524 100644
--- a/binder_bindings/android/brillo/IUpdateEngine.aidl
+++ b/binder_bindings/android/brillo/IUpdateEngine.aidl
@@ -34,6 +34,8 @@
   void SetP2PUpdatePermission(in boolean enabled);
   boolean GetP2PUpdatePermission();
   void SetUpdateOverCellularPermission(in boolean enabled);
+  void SetUpdateOverCellularTarget(in String target_version,
+                                   in long target_size);
   boolean GetUpdateOverCellularPermission();
   long GetDurationSinceUpdate();
   String GetPrevVersion();
diff --git a/binder_service_brillo.cc b/binder_service_brillo.cc
index 3f01e42..d082add 100644
--- a/binder_service_brillo.cc
+++ b/binder_service_brillo.cc
@@ -153,6 +153,13 @@
       &UpdateEngineService::SetUpdateOverCellularPermission, enabled);
 }
 
+Status BinderUpdateEngineBrilloService::SetUpdateOverCellularTarget(
+    const String16& target_version, int64_t target_size) {
+  return CallCommonHandler(&UpdateEngineService::SetUpdateOverCellularTarget,
+                           NormalString(target_version),
+                           target_size);
+}
+
 Status BinderUpdateEngineBrilloService::GetUpdateOverCellularPermission(
     bool* out_cellular_permission) {
   return CallCommonHandler(
diff --git a/binder_service_brillo.h b/binder_service_brillo.h
index c802fca..d0d0dc9 100644
--- a/binder_service_brillo.h
+++ b/binder_service_brillo.h
@@ -75,6 +75,8 @@
       bool* out_p2p_permission) override;
   android::binder::Status SetUpdateOverCellularPermission(
       bool enabled) override;
+  android::binder::Status SetUpdateOverCellularTarget(
+      const android::String16& target_version, int64_t target_size) override;
   android::binder::Status GetUpdateOverCellularPermission(
       bool* out_cellular_permission) override;
   android::binder::Status GetDurationSinceUpdate(
diff --git a/client_library/include/update_engine/update_status.h b/client_library/include/update_engine/update_status.h
index 41fab48..5a3dccf 100644
--- a/client_library/include/update_engine/update_status.h
+++ b/client_library/include/update_engine/update_status.h
@@ -23,17 +23,24 @@
 
 namespace update_engine {
 
+// ATTENTION: When adding a new enum value here, always append at the end and
+// make sure to make proper adjustments in UpdateAttempter:ActionCompleted(). If
+// any enum memeber is deprecated, the assigned value of other members should
+// not change. See b/62842358.
 enum class UpdateStatus {
   IDLE = 0,
-  CHECKING_FOR_UPDATE,
-  UPDATE_AVAILABLE,
-  DOWNLOADING,
-  VERIFYING,
-  FINALIZING,
-  UPDATED_NEED_REBOOT,
-  REPORTING_ERROR_EVENT,
-  ATTEMPTING_ROLLBACK,
-  DISABLED,
+  CHECKING_FOR_UPDATE = 1,
+  UPDATE_AVAILABLE = 2,
+  DOWNLOADING = 3,
+  VERIFYING = 4,
+  FINALIZING = 5,
+  UPDATED_NEED_REBOOT = 6,
+  REPORTING_ERROR_EVENT = 7,
+  ATTEMPTING_ROLLBACK = 8,
+  DISABLED = 9,
+  // Broadcast this state when an update aborts because user preferences do not
+  // allow updates, e.g. over cellular network.
+  NEED_PERMISSION_TO_UPDATE = 10,
 };
 
 // Enum of bit-wise flags for controlling how updates are attempted.
diff --git a/common/action_processor.cc b/common/action_processor.cc
index 3549e08..ead99c4 100644
--- a/common/action_processor.cc
+++ b/common/action_processor.cc
@@ -17,6 +17,7 @@
 #include "update_engine/common/action_processor.h"
 
 #include <string>
+#include <utility>
 
 #include <base/logging.h>
 
@@ -24,27 +25,30 @@
 #include "update_engine/common/error_code_utils.h"
 
 using std::string;
+using std::unique_ptr;
 
 namespace chromeos_update_engine {
 
 ActionProcessor::~ActionProcessor() {
   if (IsRunning())
     StopProcessing();
-  for (auto action : actions_)
-    action->SetProcessor(nullptr);
 }
 
-void ActionProcessor::EnqueueAction(AbstractAction* action) {
-  actions_.push_back(action);
+void ActionProcessor::EnqueueAction(unique_ptr<AbstractAction> action) {
   action->SetProcessor(this);
+  actions_.push_back(std::move(action));
+}
+
+bool ActionProcessor::IsRunning() const {
+  return current_action_ != nullptr || suspended_;
 }
 
 void ActionProcessor::StartProcessing() {
   CHECK(!IsRunning());
   if (!actions_.empty()) {
-    current_action_ = actions_.front();
-    LOG(INFO) << "ActionProcessor: starting " << current_action_->Type();
+    current_action_ = std::move(actions_.front());
     actions_.pop_front();
+    LOG(INFO) << "ActionProcessor: starting " << current_action_->Type();
     current_action_->PerformAction();
   }
 }
@@ -53,16 +57,13 @@
   CHECK(IsRunning());
   if (current_action_) {
     current_action_->TerminateProcessing();
-    current_action_->SetProcessor(nullptr);
   }
   LOG(INFO) << "ActionProcessor: aborted "
             << (current_action_ ? current_action_->Type() : "")
             << (suspended_ ? " while suspended" : "");
-  current_action_ = nullptr;
+  current_action_.reset();
   suspended_ = false;
   // Delete all the actions before calling the delegate.
-  for (auto action : actions_)
-    action->SetProcessor(nullptr);
   actions_.clear();
   if (delegate_)
     delegate_->ProcessingStopped(this);
@@ -106,13 +107,12 @@
 
 void ActionProcessor::ActionComplete(AbstractAction* actionptr,
                                      ErrorCode code) {
-  CHECK_EQ(actionptr, current_action_);
+  CHECK_EQ(actionptr, current_action_.get());
   if (delegate_)
     delegate_->ActionCompleted(this, actionptr, code);
   string old_type = current_action_->Type();
   current_action_->ActionCompleted(code);
-  current_action_->SetProcessor(nullptr);
-  current_action_ = nullptr;
+  current_action_.reset();
   LOG(INFO) << "ActionProcessor: finished "
             << (actions_.empty() ? "last action " : "") << old_type
             << (suspended_ ? " while suspended" : "")
@@ -138,7 +138,7 @@
     }
     return;
   }
-  current_action_ = actions_.front();
+  current_action_ = std::move(actions_.front());
   actions_.pop_front();
   LOG(INFO) << "ActionProcessor: starting " << current_action_->Type();
   current_action_->PerformAction();
diff --git a/common/action_processor.h b/common/action_processor.h
index c9c179e..f651b8e 100644
--- a/common/action_processor.h
+++ b/common/action_processor.h
@@ -18,6 +18,8 @@
 #define UPDATE_ENGINE_COMMON_ACTION_PROCESSOR_H_
 
 #include <deque>
+#include <memory>
+#include <vector>
 
 #include <base/macros.h>
 #include <brillo/errors/error.h>
@@ -69,10 +71,10 @@
 
   // Returns true iff the processing was started but not yet completed nor
   // stopped.
-  bool IsRunning() const { return current_action_ != nullptr || suspended_; }
+  bool IsRunning() const;
 
   // Adds another Action to the end of the queue.
-  virtual void EnqueueAction(AbstractAction* action);
+  virtual void EnqueueAction(std::unique_ptr<AbstractAction> action);
 
   // Sets/gets the current delegate. Set to null to remove a delegate.
   ActionProcessorDelegate* delegate() const { return delegate_; }
@@ -81,14 +83,17 @@
   }
 
   // Returns a pointer to the current Action that's processing.
-  AbstractAction* current_action() const {
-    return current_action_;
-  }
+  AbstractAction* current_action() const { return current_action_.get(); }
 
   // Called by an action to notify processor that it's done. Caller passes self.
+  // But this call deletes the action if there no other object has a reference
+  // to it, so in that case, the caller should not try to access any of its
+  // member variables after this call.
   void ActionComplete(AbstractAction* actionptr, ErrorCode code);
 
  private:
+  FRIEND_TEST(ActionProcessorTest, ChainActionsTest);
+
   // Continue processing actions (if any) after the last action terminated with
   // the passed error code. If there are no more actions to process, the
   // processing will terminate.
@@ -96,10 +101,10 @@
 
   // Actions that have not yet begun processing, in the order in which
   // they'll be processed.
-  std::deque<AbstractAction*> actions_;
+  std::deque<std::unique_ptr<AbstractAction>> actions_;
 
   // A pointer to the currently processing Action, if any.
-  AbstractAction* current_action_{nullptr};
+  std::unique_ptr<AbstractAction> current_action_;
 
   // The ErrorCode reported by an action that was suspended but finished while
   // being suspended. This error code is stored here to be reported back to the
diff --git a/common/action_processor_unittest.cc b/common/action_processor_unittest.cc
index 631e42d..eb646ef 100644
--- a/common/action_processor_unittest.cc
+++ b/common/action_processor_unittest.cc
@@ -17,6 +17,7 @@
 #include "update_engine/common/action_processor.h"
 
 #include <string>
+#include <utility>
 
 #include <gtest/gtest.h>
 
@@ -96,7 +97,11 @@
   void SetUp() override {
     action_processor_.set_delegate(&delegate_);
     // Silence Type() calls used for logging.
-    EXPECT_CALL(mock_action_, Type()).Times(testing::AnyNumber());
+    mock_action_.reset(new testing::StrictMock<MockAction>());
+    mock_action_ptr_ = mock_action_.get();
+    action_.reset(new ActionProcessorTestAction());
+    action_ptr_ = action_.get();
+    EXPECT_CALL(*mock_action_, Type()).Times(testing::AnyNumber());
   }
 
   void TearDown() override {
@@ -110,34 +115,35 @@
   MyActionProcessorDelegate delegate_{&action_processor_};
 
   // Common actions used during most tests.
-  testing::StrictMock<MockAction> mock_action_;
-  ActionProcessorTestAction action_;
+  std::unique_ptr<testing::StrictMock<MockAction>> mock_action_;
+  testing::StrictMock<MockAction>* mock_action_ptr_;
+  std::unique_ptr<ActionProcessorTestAction> action_;
+  ActionProcessorTestAction* action_ptr_;
 };
 
 TEST_F(ActionProcessorTest, SimpleTest) {
   EXPECT_FALSE(action_processor_.IsRunning());
-  action_processor_.EnqueueAction(&action_);
+  action_processor_.EnqueueAction(std::move(action_));
   EXPECT_FALSE(action_processor_.IsRunning());
-  EXPECT_FALSE(action_.IsRunning());
+  EXPECT_FALSE(action_ptr_->IsRunning());
   action_processor_.StartProcessing();
   EXPECT_TRUE(action_processor_.IsRunning());
-  EXPECT_TRUE(action_.IsRunning());
-  EXPECT_EQ(action_processor_.current_action(), &action_);
-  action_.CompleteAction();
+  EXPECT_TRUE(action_ptr_->IsRunning());
+  action_ptr_->CompleteAction();
   EXPECT_FALSE(action_processor_.IsRunning());
-  EXPECT_FALSE(action_.IsRunning());
+  EXPECT_EQ(action_processor_.current_action(), nullptr);
 }
 
 TEST_F(ActionProcessorTest, DelegateTest) {
-  action_processor_.EnqueueAction(&action_);
+  action_processor_.EnqueueAction(std::move(action_));
   action_processor_.StartProcessing();
-  action_.CompleteAction();
+  action_ptr_->CompleteAction();
   EXPECT_TRUE(delegate_.processing_done_called_);
   EXPECT_TRUE(delegate_.action_completed_called_);
 }
 
 TEST_F(ActionProcessorTest, StopProcessingTest) {
-  action_processor_.EnqueueAction(&action_);
+  action_processor_.EnqueueAction(std::move(action_));
   action_processor_.StartProcessing();
   action_processor_.StopProcessing();
   EXPECT_TRUE(delegate_.processing_stopped_called_);
@@ -150,54 +156,58 @@
   // This test doesn't use a delegate since it terminates several actions.
   action_processor_.set_delegate(nullptr);
 
-  ActionProcessorTestAction action1, action2;
-  action_processor_.EnqueueAction(&action1);
-  action_processor_.EnqueueAction(&action2);
+  auto action0 = std::make_unique<ActionProcessorTestAction>();
+  auto action1 = std::make_unique<ActionProcessorTestAction>();
+  auto action2 = std::make_unique<ActionProcessorTestAction>();
+  auto action0_ptr = action0.get();
+  auto action1_ptr = action1.get();
+  auto action2_ptr = action2.get();
+  action_processor_.EnqueueAction(std::move(action0));
+  action_processor_.EnqueueAction(std::move(action1));
+  action_processor_.EnqueueAction(std::move(action2));
+
+  EXPECT_EQ(action_processor_.actions_.size(), 3u);
+  EXPECT_EQ(action_processor_.actions_[0].get(), action0_ptr);
+  EXPECT_EQ(action_processor_.actions_[1].get(), action1_ptr);
+  EXPECT_EQ(action_processor_.actions_[2].get(), action2_ptr);
+
   action_processor_.StartProcessing();
-  EXPECT_EQ(&action1, action_processor_.current_action());
+  EXPECT_EQ(action0_ptr, action_processor_.current_action());
   EXPECT_TRUE(action_processor_.IsRunning());
-  action1.CompleteAction();
-  EXPECT_EQ(&action2, action_processor_.current_action());
+  action0_ptr->CompleteAction();
+  EXPECT_EQ(action1_ptr, action_processor_.current_action());
   EXPECT_TRUE(action_processor_.IsRunning());
-  action2.CompleteAction();
+  action1_ptr->CompleteAction();
+  EXPECT_EQ(action2_ptr, action_processor_.current_action());
+  EXPECT_TRUE(action_processor_.actions_.empty());
+  EXPECT_TRUE(action_processor_.IsRunning());
+  action2_ptr->CompleteAction();
   EXPECT_EQ(nullptr, action_processor_.current_action());
+  EXPECT_TRUE(action_processor_.actions_.empty());
   EXPECT_FALSE(action_processor_.IsRunning());
 }
 
-TEST_F(ActionProcessorTest, DtorTest) {
-  ActionProcessorTestAction action1, action2;
-  {
-    ActionProcessor action_processor;
-    action_processor.EnqueueAction(&action1);
-    action_processor.EnqueueAction(&action2);
-    action_processor.StartProcessing();
-  }
-  EXPECT_EQ(nullptr, action1.processor());
-  EXPECT_FALSE(action1.IsRunning());
-  EXPECT_EQ(nullptr, action2.processor());
-  EXPECT_FALSE(action2.IsRunning());
-}
-
 TEST_F(ActionProcessorTest, DefaultDelegateTest) {
-  // Just make sure it doesn't crash
-  action_processor_.EnqueueAction(&action_);
+  // Just make sure it doesn't crash.
+  action_processor_.EnqueueAction(std::move(action_));
   action_processor_.StartProcessing();
-  action_.CompleteAction();
+  action_ptr_->CompleteAction();
 
-  action_processor_.EnqueueAction(&action_);
+  action_.reset(new ActionProcessorTestAction());
+  action_processor_.EnqueueAction(std::move(action_));
   action_processor_.StartProcessing();
   action_processor_.StopProcessing();
 }
 
-// This test suspends and resume the action processor while running one action_.
+// This test suspends and resume the action processor while running one action.
 TEST_F(ActionProcessorTest, SuspendResumeTest) {
-  action_processor_.EnqueueAction(&mock_action_);
+  action_processor_.EnqueueAction(std::move(mock_action_));
 
   testing::InSequence s;
-  EXPECT_CALL(mock_action_, PerformAction());
+  EXPECT_CALL(*mock_action_ptr_, PerformAction());
   action_processor_.StartProcessing();
 
-  EXPECT_CALL(mock_action_, SuspendAction());
+  EXPECT_CALL(*mock_action_ptr_, SuspendAction());
   action_processor_.SuspendProcessing();
   // Suspending the processor twice should not suspend the action twice.
   action_processor_.SuspendProcessing();
@@ -205,32 +215,31 @@
   // IsRunning should return whether there's is an action doing some work, even
   // if it is suspended.
   EXPECT_TRUE(action_processor_.IsRunning());
-  EXPECT_EQ(&mock_action_, action_processor_.current_action());
+  EXPECT_EQ(mock_action_ptr_, action_processor_.current_action());
 
-  EXPECT_CALL(mock_action_, ResumeAction());
+  EXPECT_CALL(*mock_action_ptr_, ResumeAction());
   action_processor_.ResumeProcessing();
 
   // Calling ResumeProcessing twice should not affect the action_.
   action_processor_.ResumeProcessing();
-
-  action_processor_.ActionComplete(&mock_action_, ErrorCode::kSuccess);
+  action_processor_.ActionComplete(mock_action_ptr_, ErrorCode::kSuccess);
 }
 
 // This test suspends an action that presumably doesn't support suspend/resume
 // and it finished before being resumed.
 TEST_F(ActionProcessorTest, ActionCompletedWhileSuspendedTest) {
-  action_processor_.EnqueueAction(&mock_action_);
+  action_processor_.EnqueueAction(std::move(mock_action_));
 
   testing::InSequence s;
-  EXPECT_CALL(mock_action_, PerformAction());
+  EXPECT_CALL(*mock_action_ptr_, PerformAction());
   action_processor_.StartProcessing();
 
-  EXPECT_CALL(mock_action_, SuspendAction());
+  EXPECT_CALL(*mock_action_ptr_, SuspendAction());
   action_processor_.SuspendProcessing();
 
   // Simulate the action completion while suspended. No other call to
   // |mock_action_| is expected at this point.
-  action_processor_.ActionComplete(&mock_action_, ErrorCode::kSuccess);
+  action_processor_.ActionComplete(mock_action_ptr_, ErrorCode::kSuccess);
 
   // The processing should not be done since the ActionProcessor is suspended
   // and the processing is considered to be still running until resumed.
@@ -243,15 +252,15 @@
 }
 
 TEST_F(ActionProcessorTest, StoppedWhileSuspendedTest) {
-  action_processor_.EnqueueAction(&mock_action_);
+  action_processor_.EnqueueAction(std::move(mock_action_));
 
   testing::InSequence s;
-  EXPECT_CALL(mock_action_, PerformAction());
+  EXPECT_CALL(*mock_action_ptr_, PerformAction());
   action_processor_.StartProcessing();
-  EXPECT_CALL(mock_action_, SuspendAction());
+  EXPECT_CALL(*mock_action_ptr_, SuspendAction());
   action_processor_.SuspendProcessing();
 
-  EXPECT_CALL(mock_action_, TerminateProcessing());
+  EXPECT_CALL(*mock_action_ptr_, TerminateProcessing());
   action_processor_.StopProcessing();
   // Stopping the processing should abort the current execution no matter what.
   EXPECT_TRUE(delegate_.processing_stopped_called_);
diff --git a/common/action_unittest.cc b/common/action_unittest.cc
index dcdce17..b2f9ba4 100644
--- a/common/action_unittest.cc
+++ b/common/action_unittest.cc
@@ -16,8 +16,11 @@
 
 #include "update_engine/common/action.h"
 
-#include <gtest/gtest.h>
 #include <string>
+#include <utility>
+
+#include <gtest/gtest.h>
+
 #include "update_engine/common/action_processor.h"
 
 using std::string;
@@ -56,21 +59,19 @@
 // This test creates two simple Actions and sends a message via an ActionPipe
 // from one to the other.
 TEST(ActionTest, SimpleTest) {
-  ActionTestAction action;
-
-  EXPECT_FALSE(action.in_pipe());
-  EXPECT_FALSE(action.out_pipe());
-  EXPECT_FALSE(action.processor());
-  EXPECT_FALSE(action.IsRunning());
+  auto action = std::make_unique<ActionTestAction>();
+  auto action_ptr = action.get();
+  EXPECT_FALSE(action->in_pipe());
+  EXPECT_FALSE(action->out_pipe());
+  EXPECT_FALSE(action->processor());
+  EXPECT_FALSE(action->IsRunning());
 
   ActionProcessor action_processor;
-  action_processor.EnqueueAction(&action);
-  EXPECT_EQ(&action_processor, action.processor());
-
+  action_processor.EnqueueAction(std::move(action));
+  EXPECT_EQ(&action_processor, action_ptr->processor());
   action_processor.StartProcessing();
-  EXPECT_TRUE(action.IsRunning());
-  action.CompleteAction();
-  EXPECT_FALSE(action.IsRunning());
+  EXPECT_TRUE(action_ptr->IsRunning());
+  action_ptr->CompleteAction();
 }
 
 }  // namespace chromeos_update_engine
diff --git a/common/constants.cc b/common/constants.cc
index 5941c93..2edfbb7 100644
--- a/common/constants.cc
+++ b/common/constants.cc
@@ -47,6 +47,7 @@
     "metrics-attempt-last-reporting-time";
 const char kPrefsMetricsCheckLastReportingTime[] =
     "metrics-check-last-reporting-time";
+const char kPrefsNoIgnoreBackoff[] = "no-ignore-backoff";
 const char kPrefsNumReboots[] = "num-reboots";
 const char kPrefsNumResponsesSeen[] = "num-responses-seen";
 const char kPrefsOmahaCohort[] = "omaha-cohort";
@@ -60,6 +61,7 @@
 const char kPrefsPostInstallSucceeded[] = "post-install-succeeded";
 const char kPrefsPreviousVersion[] = "previous-version";
 const char kPrefsResumedUpdateFailures[] = "resumed-update-failures";
+const char kPrefsRollbackHappened[] = "rollback-happened";
 const char kPrefsRollbackVersion[] = "rollback-version";
 const char kPrefsChannelOnSlotPrefix[] = "channel-on-slot-";
 const char kPrefsSystemUpdatedMarker[] = "system-updated-marker";
@@ -75,6 +77,10 @@
 const char kPrefsUpdateFirstSeenAt[] = "update-first-seen-at";
 const char kPrefsUpdateOverCellularPermission[] =
     "update-over-cellular-permission";
+const char kPrefsUpdateOverCellularTargetVersion[] =
+    "update-over-cellular-target-version";
+const char kPrefsUpdateOverCellularTargetSize[] =
+    "update-over-cellular-target-size";
 const char kPrefsUpdateServerCertificate[] = "update-server-cert";
 const char kPrefsUpdateStateNextDataLength[] = "update-state-next-data-length";
 const char kPrefsUpdateStateNextDataOffset[] = "update-state-next-data-offset";
@@ -86,7 +92,9 @@
     "update-state-signed-sha-256-context";
 const char kPrefsUpdateTimestampStart[] = "update-timestamp-start";
 const char kPrefsUrlSwitchCount[] = "url-switch-count";
-const char kPrefsWallClockWaitPeriod[] = "wall-clock-wait-period";
+const char kPrefsWallClockScatteringWaitPeriod[] = "wall-clock-wait-period";
+const char kPrefsWallClockStagingWaitPeriod[] =
+    "wall-clock-staging-wait-period";
 
 // 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 fc15fce..d97c5fb 100644
--- a/common/constants.h
+++ b/common/constants.h
@@ -49,6 +49,7 @@
 extern const char kPrefsManifestSignatureSize[];
 extern const char kPrefsMetricsAttemptLastReportingTime[];
 extern const char kPrefsMetricsCheckLastReportingTime[];
+extern const char kPrefsNoIgnoreBackoff[];
 extern const char kPrefsNumReboots[];
 extern const char kPrefsNumResponsesSeen[];
 extern const char kPrefsOmahaCohort[];
@@ -62,6 +63,7 @@
 extern const char kPrefsPostInstallSucceeded[];
 extern const char kPrefsPreviousVersion[];
 extern const char kPrefsResumedUpdateFailures[];
+extern const char kPrefsRollbackHappened[];
 extern const char kPrefsRollbackVersion[];
 extern const char kPrefsChannelOnSlotPrefix[];
 extern const char kPrefsSystemUpdatedMarker[];
@@ -76,6 +78,8 @@
 extern const char kPrefsUpdateDurationUptime[];
 extern const char kPrefsUpdateFirstSeenAt[];
 extern const char kPrefsUpdateOverCellularPermission[];
+extern const char kPrefsUpdateOverCellularTargetVersion[];
+extern const char kPrefsUpdateOverCellularTargetSize[];
 extern const char kPrefsUpdateServerCertificate[];
 extern const char kPrefsUpdateStateNextDataLength[];
 extern const char kPrefsUpdateStateNextDataOffset[];
@@ -86,7 +90,8 @@
 extern const char kPrefsUpdateStateSignedSHA256Context[];
 extern const char kPrefsUpdateTimestampStart[];
 extern const char kPrefsUrlSwitchCount[];
-extern const char kPrefsWallClockWaitPeriod[];
+extern const char kPrefsWallClockScatteringWaitPeriod[];
+extern const char kPrefsWallClockStagingWaitPeriod[];
 
 // Keys used when storing and loading payload properties.
 extern const char kPayloadPropertyFileSize[];
diff --git a/common/error_code.h b/common/error_code.h
index f7f75a9..86b7a3e 100644
--- a/common/error_code.h
+++ b/common/error_code.h
@@ -73,14 +73,16 @@
   kFilesystemVerifierError = 47,
   kUserCanceled = 48,
   kNonCriticalUpdateInOOBE = 49,
-  // kOmahaUpdateIgnoredOverCellular = 50,
+  kOmahaUpdateIgnoredOverCellular = 50,
   kPayloadTimestampError = 51,
   kUpdatedButNotActive = 52,
   kNoUpdate = 53,
+  kRollbackNotPossible = 54,
+  kFirstActiveOmahaPingSentPersistenceError = 55,
 
   // VERY IMPORTANT! When adding new error codes:
   //
-  // 1) Update tools/metrics/histograms/histograms.xml in Chrome.
+  // 1) Update tools/metrics/histograms/enums.xml in Chrome.
   //
   // 2) Update the assorted switch statements in update_engine which won't
   //    build until this case is added.
diff --git a/common/error_code_utils.cc b/common/error_code_utils.cc
index 8b75329..930dafe 100644
--- a/common/error_code_utils.cc
+++ b/common/error_code_utils.cc
@@ -144,12 +144,18 @@
       return "ErrorCode::kUserCanceled";
     case ErrorCode::kNonCriticalUpdateInOOBE:
       return "ErrorCode::kNonCriticalUpdateInOOBE";
+    case ErrorCode::kOmahaUpdateIgnoredOverCellular:
+      return "ErrorCode::kOmahaUpdateIgnoredOverCellular";
     case ErrorCode::kPayloadTimestampError:
       return "ErrorCode::kPayloadTimestampError";
     case ErrorCode::kUpdatedButNotActive:
       return "ErrorCode::kUpdatedButNotActive";
     case ErrorCode::kNoUpdate:
       return "ErrorCode::kNoUpdate";
+    case ErrorCode::kRollbackNotPossible:
+      return "ErrorCode::kRollbackNotPossible";
+    case ErrorCode::kFirstActiveOmahaPingSentPersistenceError:
+      return "ErrorCode::kFirstActiveOmahaPingSentPersistenceError";
       // Don't add a default case to let the compiler warn about newly added
       // error codes which should be added here.
   }
diff --git a/common/fake_hardware.h b/common/fake_hardware.h
index f2b2c9d..55ef32d 100644
--- a/common/fake_hardware.h
+++ b/common/fake_hardware.h
@@ -34,6 +34,24 @@
   // false.
   static const int kPowerwashCountNotSet = -1;
 
+  // Default value for crossystem tpm_kernver.
+  static const int kMinKernelKeyVersion = 3;
+
+  // Default value for crossystem tpm_fwver.
+  static const int kMinFirmwareKeyVersion = 13;
+
+  // Default value for crossystem kernel_max_rollforward. This value is the
+  // default for consumer devices and effectively means "unlimited rollforward
+  // is allowed", which is the same as the behavior prior to implementing
+  // roll forward prevention.
+  static const int kKernelMaxRollforward = 0xfffffffe;
+
+  // Default value for crossystem firmware_max_rollforward. This value is the
+  // default for consumer devices and effectively means "unlimited rollforward
+  // is allowed", which is the same as the behavior prior to implementing
+  // roll forward prevention.
+  static const int kFirmwareMaxRollforward = 0xfffffffe;
+
   FakeHardware() = default;
 
   // HardwareInterface methods.
@@ -59,6 +77,31 @@
 
   std::string GetECVersion() const override { return ec_version_; }
 
+  int GetMinKernelKeyVersion() const override {
+    return min_kernel_key_version_;
+  }
+
+  int GetMinFirmwareKeyVersion() const override {
+    return min_firmware_key_version_;
+  }
+
+  int GetMaxFirmwareKeyRollforward() const override {
+    return firmware_max_rollforward_;
+  }
+
+  bool SetMaxFirmwareKeyRollforward(int firmware_max_rollforward) override {
+    if (GetMaxFirmwareKeyRollforward() == -1)
+      return false;
+
+    firmware_max_rollforward_ = firmware_max_rollforward;
+    return true;
+  }
+
+  bool SetMaxKernelKeyRollforward(int kernel_max_rollforward) override {
+    kernel_max_rollforward_ = kernel_max_rollforward;
+    return true;
+  }
+
   int GetPowerwashCount() const override { return powerwash_count_; }
 
   bool SchedulePowerwash() override {
@@ -87,8 +130,9 @@
     return first_active_omaha_ping_sent_;
   }
 
-  void SetFirstActiveOmahaPingSent() override {
+  bool SetFirstActiveOmahaPingSent() override {
     first_active_omaha_ping_sent_ = true;
+    return true;
   }
 
   // Setters
@@ -131,6 +175,14 @@
     ec_version_ = ec_version;
   }
 
+  void SetMinKernelKeyVersion(int min_kernel_key_version) {
+    min_kernel_key_version_ = min_kernel_key_version;
+  }
+
+  void SetMinFirmwareKeyVersion(int min_firmware_key_version) {
+    min_firmware_key_version_ = min_firmware_key_version;
+  }
+
   void SetPowerwashCount(int powerwash_count) {
     powerwash_count_ = powerwash_count;
   }
@@ -139,16 +191,24 @@
     build_timestamp_ = build_timestamp;
   }
 
+  // Getters to verify state.
+  int GetMaxKernelKeyRollforward() const { return kernel_max_rollforward_; }
+
  private:
   bool is_official_build_{true};
   bool is_normal_boot_mode_{true};
   bool are_dev_features_enabled_{false};
   bool is_oobe_enabled_{true};
   bool is_oobe_complete_{true};
-  base::Time oobe_timestamp_{base::Time::FromTimeT(1169280000)}; // Jan 20, 2007
+  // Jan 20, 2007
+  base::Time oobe_timestamp_{base::Time::FromTimeT(1169280000)};
   std::string hardware_class_{"Fake HWID BLAH-1234"};
   std::string firmware_version_{"Fake Firmware v1.0.1"};
   std::string ec_version_{"Fake EC v1.0a"};
+  int min_kernel_key_version_{kMinKernelKeyVersion};
+  int min_firmware_key_version_{kMinFirmwareKeyVersion};
+  int kernel_max_rollforward_{kKernelMaxRollforward};
+  int firmware_max_rollforward_{kFirmwareMaxRollforward};
   int powerwash_count_{kPowerwashCountNotSet};
   bool powerwash_scheduled_{false};
   int64_t build_timestamp_{0};
diff --git a/common/file_fetcher.cc b/common/file_fetcher.cc
index d0a109b..3836e54 100644
--- a/common/file_fetcher.cc
+++ b/common/file_fetcher.cc
@@ -138,8 +138,9 @@
       delegate_->TransferComplete(this, true);
   } else {
     bytes_copied_ += bytes_read;
-    if (delegate_)
-      delegate_->ReceivedBytes(this, buffer_.data(), bytes_read);
+    if (delegate_ &&
+        !delegate_->ReceivedBytes(this, buffer_.data(), bytes_read))
+      return;
     ScheduleRead();
   }
 }
diff --git a/common/hardware_interface.h b/common/hardware_interface.h
index 94442d1..bbc8660 100644
--- a/common/hardware_interface.h
+++ b/common/hardware_interface.h
@@ -70,6 +70,32 @@
   // running a custom chrome os ec.
   virtual std::string GetECVersion() const = 0;
 
+  // Returns the minimum kernel key version that verified boot on Chrome OS
+  // will allow to boot. This is the value of crossystem tpm_kernver. Returns
+  // -1 on error, or if not running on Chrome OS.
+  virtual int GetMinKernelKeyVersion() const = 0;
+
+  // Returns the minimum firmware key version that verified boot on Chrome OS
+  // will allow to boot. This is the value of crossystem tpm_fwver. Returns
+  // -1 on error, or if not running on Chrome OS.
+  virtual int GetMinFirmwareKeyVersion() const = 0;
+
+  // Returns the maximum firmware key version that verified boot should roll
+  // forward to. This is the value of crossystem firmware_max_rollforward.
+  // Returns -1 on error, if this board does not yet support this value, or
+  // if not running on Chrome OS.
+  virtual int GetMaxFirmwareKeyRollforward() const = 0;
+
+  // Sets the maximum firmware key version that verified boot should roll
+  // forward to. This is the value of crossystem firmware_max_rollforward.
+  // This value is not available on all Chrome OS devices.
+  virtual bool SetMaxFirmwareKeyRollforward(int firmware_max_rollforward) = 0;
+
+  // Sets the maximum kernel key version that verified boot should roll
+  // forward to. This is the value of crossystem kernel_max_rollforward.
+  // Returns false if the value cannot be set, or if not running on Chrome OS.
+  virtual bool SetMaxKernelKeyRollforward(int kernel_max_rollforward) = 0;
+
   // Returns the powerwash_count from the stateful. If the file is not found
   // or is invalid, returns -1. Brand new machines out of the factory or after
   // recovery don't have this value set.
@@ -100,9 +126,9 @@
   // |SetFirstActiveOmahaPingSent()|.
   virtual bool GetFirstActiveOmahaPingSent() const = 0;
 
-  // Persist the fact that first active ping was sent to omaha. It bails out if
-  // it fails.
-  virtual void SetFirstActiveOmahaPingSent() = 0;
+  // Persist the fact that first active ping was sent to omaha and returns false
+  // if failed to persist it.
+  virtual bool SetFirstActiveOmahaPingSent() = 0;
 };
 
 }  // namespace chromeos_update_engine
diff --git a/common/http_fetcher.h b/common/http_fetcher.h
index 3f7b2e8..b2fba1c 100644
--- a/common/http_fetcher.h
+++ b/common/http_fetcher.h
@@ -18,6 +18,7 @@
 #define UPDATE_ENGINE_COMMON_HTTP_FETCHER_H_
 
 #include <deque>
+#include <memory>
 #include <string>
 #include <vector>
 
@@ -186,8 +187,9 @@
  public:
   virtual ~HttpFetcherDelegate() = default;
 
-  // Called every time bytes are received.
-  virtual void ReceivedBytes(HttpFetcher* fetcher,
+  // Called every time bytes are received. Returns false if this call causes the
+  // transfer be terminated or completed otherwise it returns true.
+  virtual bool ReceivedBytes(HttpFetcher* fetcher,
                              const void* bytes,
                              size_t length) = 0;
 
diff --git a/common/http_fetcher_unittest.cc b/common/http_fetcher_unittest.cc
index 867216e..23df67a 100644
--- a/common/http_fetcher_unittest.cc
+++ b/common/http_fetcher_unittest.cc
@@ -411,12 +411,13 @@
  public:
   HttpFetcherTestDelegate() = default;
 
-  void ReceivedBytes(HttpFetcher* /* fetcher */,
+  bool ReceivedBytes(HttpFetcher* /* fetcher */,
                      const void* bytes,
                      size_t length) override {
     data.append(reinterpret_cast<const char*>(bytes), length);
     // Update counters
     times_received_bytes_called_++;
+    return true;
   }
 
   void TransferComplete(HttpFetcher* fetcher, bool successful) override {
@@ -559,11 +560,13 @@
 namespace {
 class PausingHttpFetcherTestDelegate : public HttpFetcherDelegate {
  public:
-  void ReceivedBytes(HttpFetcher* fetcher,
-                     const void* /* bytes */, size_t /* length */) override {
+  bool ReceivedBytes(HttpFetcher* fetcher,
+                     const void* /* bytes */,
+                     size_t /* length */) override {
     CHECK(!paused_);
     paused_ = true;
     fetcher->Pause();
+    return true;
   }
   void TransferComplete(HttpFetcher* fetcher, bool successful) override {
     MessageLoop::current()->BreakLoop();
@@ -640,8 +643,11 @@
 namespace {
 class AbortingHttpFetcherTestDelegate : public HttpFetcherDelegate {
  public:
-  void ReceivedBytes(HttpFetcher* fetcher,
-                     const void* bytes, size_t length) override {}
+  bool ReceivedBytes(HttpFetcher* fetcher,
+                     const void* bytes,
+                     size_t length) override {
+    return true;
+  }
   void TransferComplete(HttpFetcher* fetcher, bool successful) override {
     ADD_FAILURE();  // We should never get here
     MessageLoop::current()->BreakLoop();
@@ -735,9 +741,11 @@
 namespace {
 class FlakyHttpFetcherTestDelegate : public HttpFetcherDelegate {
  public:
-  void ReceivedBytes(HttpFetcher* fetcher,
-                     const void* bytes, size_t length) override {
+  bool ReceivedBytes(HttpFetcher* fetcher,
+                     const void* bytes,
+                     size_t length) override {
     data.append(reinterpret_cast<const char*>(bytes), length);
+    return true;
   }
   void TransferComplete(HttpFetcher* fetcher, bool successful) override {
     EXPECT_TRUE(successful);
@@ -799,13 +807,15 @@
     }
   }
 
-  void ReceivedBytes(HttpFetcher* fetcher,
-                     const void* bytes, size_t length) override {
+  bool ReceivedBytes(HttpFetcher* fetcher,
+                     const void* bytes,
+                     size_t length) override {
     if (server_) {
       LOG(INFO) << "Stopping server in ReceivedBytes";
       server_.reset();
       LOG(INFO) << "server stopped";
     }
+    return true;
   }
   void TransferComplete(HttpFetcher* fetcher, bool successful) override {
     EXPECT_FALSE(successful);
@@ -973,9 +983,11 @@
  public:
   explicit RedirectHttpFetcherTestDelegate(bool expected_successful)
       : expected_successful_(expected_successful) {}
-  void ReceivedBytes(HttpFetcher* fetcher,
-                     const void* bytes, size_t length) override {
+  bool ReceivedBytes(HttpFetcher* fetcher,
+                     const void* bytes,
+                     size_t length) override {
     data.append(reinterpret_cast<const char*>(bytes), length);
+    return true;
   }
   void TransferComplete(HttpFetcher* fetcher, bool successful) override {
     EXPECT_EQ(expected_successful_, successful);
@@ -1072,10 +1084,12 @@
   explicit MultiHttpFetcherTestDelegate(int expected_response_code)
       : expected_response_code_(expected_response_code) {}
 
-  void ReceivedBytes(HttpFetcher* fetcher,
-                     const void* bytes, size_t length) override {
+  bool ReceivedBytes(HttpFetcher* fetcher,
+                     const void* bytes,
+                     size_t length) override {
     EXPECT_EQ(fetcher, fetcher_.get());
     data.append(reinterpret_cast<const char*>(bytes), length);
+    return true;
   }
 
   void TransferComplete(HttpFetcher* fetcher, bool successful) override {
@@ -1271,7 +1285,7 @@
   explicit MultiHttpFetcherTerminateTestDelegate(size_t terminate_trigger_bytes)
       : terminate_trigger_bytes_(terminate_trigger_bytes) {}
 
-  void ReceivedBytes(HttpFetcher* fetcher,
+  bool ReceivedBytes(HttpFetcher* fetcher,
                      const void* bytes,
                      size_t length) override {
     LOG(INFO) << "ReceivedBytes, " << length << " bytes.";
@@ -1284,6 +1298,7 @@
                      base::Unretained(fetcher_.get())));
     }
     bytes_downloaded_ += length;
+    return true;
   }
 
   void TransferComplete(HttpFetcher* fetcher, bool successful) override {
@@ -1337,9 +1352,11 @@
 namespace {
 class BlockedTransferTestDelegate : public HttpFetcherDelegate {
  public:
-  void ReceivedBytes(HttpFetcher* fetcher,
-                     const void* bytes, size_t length) override {
+  bool ReceivedBytes(HttpFetcher* fetcher,
+                     const void* bytes,
+                     size_t length) override {
     ADD_FAILURE();
+    return true;
   }
   void TransferComplete(HttpFetcher* fetcher, bool successful) override {
     EXPECT_FALSE(successful);
diff --git a/common/mock_action_processor.h b/common/mock_action_processor.h
index 04275c1..4c62109 100644
--- a/common/mock_action_processor.h
+++ b/common/mock_action_processor.h
@@ -17,6 +17,10 @@
 #ifndef UPDATE_ENGINE_COMMON_MOCK_ACTION_PROCESSOR_H_
 #define UPDATE_ENGINE_COMMON_MOCK_ACTION_PROCESSOR_H_
 
+#include <deque>
+#include <memory>
+#include <utility>
+
 #include <gmock/gmock.h>
 
 #include "update_engine/common/action.h"
@@ -27,6 +31,12 @@
  public:
   MOCK_METHOD0(StartProcessing, void());
   MOCK_METHOD1(EnqueueAction, void(AbstractAction* action));
+
+  // This is a legacy workaround described in:
+  // https://github.com/google/googletest/blob/master/googlemock/docs/CookBook.md#legacy-workarounds-for-move-only-types-legacymoveonly
+  void EnqueueAction(std::unique_ptr<AbstractAction> action) override {
+    EnqueueAction(action.get());
+  }
 };
 
 }  // namespace chromeos_update_engine
diff --git a/common/mock_hardware.h b/common/mock_hardware.h
index 42fa7ba..f972df2 100644
--- a/common/mock_hardware.h
+++ b/common/mock_hardware.h
@@ -54,6 +54,21 @@
     ON_CALL(*this, GetECVersion())
       .WillByDefault(testing::Invoke(&fake_,
             &FakeHardware::GetECVersion));
+    ON_CALL(*this, GetMinKernelKeyVersion())
+        .WillByDefault(
+            testing::Invoke(&fake_, &FakeHardware::GetMinKernelKeyVersion));
+    ON_CALL(*this, GetMinFirmwareKeyVersion())
+        .WillByDefault(
+            testing::Invoke(&fake_, &FakeHardware::GetMinFirmwareKeyVersion));
+    ON_CALL(*this, GetMaxFirmwareKeyRollforward())
+        .WillByDefault(testing::Invoke(
+            &fake_, &FakeHardware::GetMaxFirmwareKeyRollforward));
+    ON_CALL(*this, SetMaxFirmwareKeyRollforward())
+        .WillByDefault(testing::Invoke(
+            &fake_, &FakeHardware::SetMaxFirmwareKeyRollforward));
+    ON_CALL(*this, SetMaxKernelKeyRollforward())
+        .WillByDefault(
+            testing::Invoke(&fake_, &FakeHardware::SetMaxKernelKeyRollforward));
     ON_CALL(*this, GetPowerwashCount())
       .WillByDefault(testing::Invoke(&fake_,
             &FakeHardware::GetPowerwashCount));
@@ -81,6 +96,13 @@
   MOCK_CONST_METHOD0(GetHardwareClass, std::string());
   MOCK_CONST_METHOD0(GetFirmwareVersion, std::string());
   MOCK_CONST_METHOD0(GetECVersion, std::string());
+  MOCK_CONST_METHOD0(GetMinKernelKeyVersion, int());
+  MOCK_CONST_METHOD0(GetMinFirmwareKeyVersion, int());
+  MOCK_CONST_METHOD0(GetMaxFirmwareKeyRollforward, int());
+  MOCK_CONST_METHOD1(SetMaxFirmwareKeyRollforward,
+                     bool(int firmware_max_rollforward));
+  MOCK_CONST_METHOD1(SetMaxKernelKeyRollforward,
+                     bool(int kernel_max_rollforward));
   MOCK_CONST_METHOD0(GetPowerwashCount, int());
   MOCK_CONST_METHOD1(GetNonVolatileDirectory, bool(base::FilePath*));
   MOCK_CONST_METHOD1(GetPowerwashSafeDirectory, bool(base::FilePath*));
diff --git a/common/mock_http_fetcher.cc b/common/mock_http_fetcher.cc
index f1ae72a..9507c9d 100644
--- a/common/mock_http_fetcher.cc
+++ b/common/mock_http_fetcher.cc
@@ -47,71 +47,49 @@
     SendData(true);
 }
 
-// Returns false on one condition: If timeout_id_ was already set
-// and it needs to be deleted by the caller. If timeout_id_ is null
-// when this function is called, this function will always return true.
-bool MockHttpFetcher::SendData(bool skip_delivery) {
-  if (fail_transfer_) {
+void MockHttpFetcher::SendData(bool skip_delivery) {
+  if (fail_transfer_ || sent_size_ == data_.size()) {
     SignalTransferComplete();
-    return timeout_id_ != MessageLoop::kTaskIdNull;
-  }
-
-  CHECK_LT(sent_size_, data_.size());
-  if (!skip_delivery) {
-    const size_t chunk_size = min(kMockHttpFetcherChunkSize,
-                                  data_.size() - sent_size_);
-    CHECK(delegate_);
-    delegate_->ReceivedBytes(this, &data_[sent_size_], chunk_size);
-    // We may get terminated in the callback.
-    if (sent_size_ == data_.size()) {
-      LOG(INFO) << "Terminated in the ReceivedBytes callback.";
-      return timeout_id_ != MessageLoop::kTaskIdNull;
-    }
-    sent_size_ += chunk_size;
-    CHECK_LE(sent_size_, data_.size());
-    if (sent_size_ == data_.size()) {
-      // We've sent all the data. Notify of success.
-      SignalTransferComplete();
-    }
+    return;
   }
 
   if (paused_) {
-    // If we're paused, we should return true if timeout_id_ is set,
-    // since we need the caller to delete it.
-    return timeout_id_ != MessageLoop::kTaskIdNull;
+    // If we're paused, we should return so no callback is scheduled.
+    return;
   }
 
-  if (timeout_id_ != MessageLoop::kTaskIdNull) {
-    // we still need a timeout if there's more data to send
-    return sent_size_ < data_.size();
-  } else if (sent_size_ < data_.size()) {
-    // we don't have a timeout source and we need one
+  // Setup timeout callback even if the transfer is about to be completed in
+  // order to get a call to |TransferComplete|.
+  if (timeout_id_ == MessageLoop::kTaskIdNull) {
     timeout_id_ = MessageLoop::current()->PostDelayedTask(
         FROM_HERE,
         base::Bind(&MockHttpFetcher::TimeoutCallback, base::Unretained(this)),
         base::TimeDelta::FromMilliseconds(10));
   }
-  return true;
+
+  if (!skip_delivery) {
+    const size_t chunk_size =
+        min(kMockHttpFetcherChunkSize, data_.size() - sent_size_);
+    sent_size_ += chunk_size;
+    CHECK(delegate_);
+    delegate_->ReceivedBytes(this, &data_[sent_size_ - chunk_size], chunk_size);
+  }
+  // We may get terminated and deleted right after |ReceivedBytes| call, so we
+  // should not access any class member variable after this call.
 }
 
 void MockHttpFetcher::TimeoutCallback() {
   CHECK(!paused_);
-  if (SendData(false)) {
-    // We need to re-schedule the timeout.
-    timeout_id_ = MessageLoop::current()->PostDelayedTask(
-        FROM_HERE,
-        base::Bind(&MockHttpFetcher::TimeoutCallback, base::Unretained(this)),
-        base::TimeDelta::FromMilliseconds(10));
-  } else {
-    timeout_id_ = MessageLoop::kTaskIdNull;
-  }
+  timeout_id_ = MessageLoop::kTaskIdNull;
+  CHECK_LE(sent_size_, data_.size());
+  // Same here, we should not access any member variable after this call.
+  SendData(false);
 }
 
 // If the transfer is in progress, aborts the transfer early.
 // The transfer cannot be resumed.
 void MockHttpFetcher::TerminateTransfer() {
   LOG(INFO) << "Terminating transfer.";
-  sent_size_ = data_.size();
   // Kill any timeout, it is ok to call with kTaskIdNull.
   MessageLoop::current()->CancelTask(timeout_id_);
   timeout_id_ = MessageLoop::kTaskIdNull;
@@ -140,9 +118,7 @@
 void MockHttpFetcher::Unpause() {
   CHECK(paused_) << "You must pause before unpause.";
   paused_ = false;
-  if (sent_size_ < data_.size()) {
-    SendData(false);
-  }
+  SendData(false);
 }
 
 void MockHttpFetcher::FailTransfer(int http_response_code) {
diff --git a/common/mock_http_fetcher.h b/common/mock_http_fetcher.h
index 367802e..00f4e2b 100644
--- a/common/mock_http_fetcher.h
+++ b/common/mock_http_fetcher.h
@@ -112,13 +112,10 @@
   }
 
  private:
-  // Sends data to the delegate and sets up a timeout callback if needed.
-  // There must be a delegate and there must be data to send. If there is
-  // already a timeout callback, and it should be deleted by the caller,
-  // this will return false; otherwise true is returned.
-  // If skip_delivery is true, no bytes will be delivered, but the callbacks
-  // still be set if needed.
-  bool SendData(bool skip_delivery);
+  // Sends data to the delegate and sets up a timeout callback if needed. There
+  // must be a delegate. If |skip_delivery| is true, no bytes will be delivered,
+  // but the callbacks still be set if needed.
+  void SendData(bool skip_delivery);
 
   // Callback for when our message loop timeout expires.
   void TimeoutCallback();
diff --git a/common/multi_range_http_fetcher.cc b/common/multi_range_http_fetcher.cc
index dc4b7c1..0a19c6a 100644
--- a/common/multi_range_http_fetcher.cc
+++ b/common/multi_range_http_fetcher.cc
@@ -86,7 +86,7 @@
 }
 
 // State change: Downloading -> Downloading or Pending transfer ended
-void MultiRangeHttpFetcher::ReceivedBytes(HttpFetcher* fetcher,
+bool MultiRangeHttpFetcher::ReceivedBytes(HttpFetcher* fetcher,
                                           const void* bytes,
                                           size_t length) {
   CHECK_LT(current_index_, ranges_.size());
@@ -99,15 +99,9 @@
                          range.length() - bytes_received_this_range_);
   }
   LOG_IF(WARNING, next_size <= 0) << "Asked to write length <= 0";
-  if (delegate_) {
-    delegate_->ReceivedBytes(this, bytes, next_size);
-    // If the transfer was already terminated by |delegate_|, return immediately
-    // to avoid calling TerminateTransfer() again.
-    if (!base_fetcher_active_) {
-      LOG(INFO) << "Delegate has terminated the transfer.";
-      return;
-    }
-  }
+  if (delegate_ && !delegate_->ReceivedBytes(this, bytes, next_size))
+    return false;
+
   bytes_received_this_range_ += length;
   if (range.HasLength() && bytes_received_this_range_ >= range.length()) {
     // Terminates the current fetcher. Waits for its TransferTerminated
@@ -115,9 +109,10 @@
     // signalling the delegate that the whole multi-transfer is complete
     // before all fetchers are really done and cleaned up.
     pending_transfer_ended_ = true;
-    LOG(INFO) << "terminating transfer";
+    LOG(INFO) << "Terminating transfer.";
     fetcher->TerminateTransfer();
   }
+  return true;
 }
 
 // State change: Downloading or Pending transfer ended -> Stopped
diff --git a/common/multi_range_http_fetcher.h b/common/multi_range_http_fetcher.h
index 54ddfbc..763c287 100644
--- a/common/multi_range_http_fetcher.h
+++ b/common/multi_range_http_fetcher.h
@@ -146,7 +146,7 @@
 
   // HttpFetcherDelegate overrides.
   // State change: Downloading -> Downloading or Pending transfer ended
-  void ReceivedBytes(HttpFetcher* fetcher,
+  bool ReceivedBytes(HttpFetcher* fetcher,
                      const void* bytes,
                      size_t length) override;
 
diff --git a/common/subprocess.cc b/common/subprocess.cc
index 4e6d352..1715cb0 100644
--- a/common/subprocess.cc
+++ b/common/subprocess.cc
@@ -23,6 +23,7 @@
 
 #include <memory>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include <base/bind.h>
@@ -281,14 +282,22 @@
   return proc_return_code != brillo::Process::kErrorExitStatus;
 }
 
-bool Subprocess::SubprocessInFlight() {
-  for (const auto& pid_record : subprocess_records_) {
-    if (!pid_record.second->callback.is_null())
-      return true;
+void Subprocess::FlushBufferedLogsAtExit() {
+  if (!subprocess_records_.empty()) {
+    LOG(INFO) << "We are exiting, but there are still in flight subprocesses!";
+    for (auto& pid_record : subprocess_records_) {
+      SubprocessRecord* record = pid_record.second.get();
+      // Make sure we read any remaining process output.
+      OnStdoutReady(record);
+      if (!record->stdout.empty()) {
+        LOG(INFO) << "Subprocess(" << pid_record.first << ") output:\n"
+                  << record->stdout;
+      }
+    }
   }
-  return false;
 }
 
+
 Subprocess* Subprocess::subprocess_singleton_ = nullptr;
 
 }  // namespace chromeos_update_engine
diff --git a/common/subprocess.h b/common/subprocess.h
index b655fb7..209158b 100644
--- a/common/subprocess.h
+++ b/common/subprocess.h
@@ -101,8 +101,10 @@
     return *subprocess_singleton_;
   }
 
-  // Returns true iff there is at least one subprocess we're waiting on.
-  bool SubprocessInFlight();
+  // Tries to log all in flight processes's output. It is used right before
+  // exiting the update_engine, probably when the subprocess caused a system
+  // shutdown.
+  void FlushBufferedLogsAtExit();
 
  private:
   FRIEND_TEST(SubprocessTest, CancelTest);
diff --git a/common/test_utils.cc b/common/test_utils.cc
index 85f78f9..04f55d0 100644
--- a/common/test_utils.cc
+++ b/common/test_utils.cc
@@ -28,7 +28,6 @@
 #include <sys/stat.h>
 #include <sys/sysmacros.h>
 #include <sys/types.h>
-#include <sys/xattr.h>
 #include <unistd.h>
 
 #include <set>
@@ -112,36 +111,6 @@
   return string(buf.begin(), buf.begin() + r);
 }
 
-bool IsXAttrSupported(const base::FilePath& dir_path) {
-  char *path = strdup(dir_path.Append("xattr_test_XXXXXX").value().c_str());
-
-  int fd = mkstemp(path);
-  if (fd == -1) {
-    PLOG(ERROR) << "Error creating temporary file in " << dir_path.value();
-    free(path);
-    return false;
-  }
-
-  if (unlink(path) != 0) {
-    PLOG(ERROR) << "Error unlinking temporary file " << path;
-    close(fd);
-    free(path);
-    return false;
-  }
-
-  int xattr_res = fsetxattr(fd, "user.xattr-test", "value", strlen("value"), 0);
-  if (xattr_res != 0) {
-    if (errno == ENOTSUP) {
-      // Leave it to call-sites to warn about non-support.
-    } else {
-      PLOG(ERROR) << "Error setting xattr on " << path;
-    }
-  }
-  close(fd);
-  free(path);
-  return xattr_res == 0;
-}
-
 bool WriteFileVector(const string& path, const brillo::Blob& data) {
   return utils::WriteFile(path.c_str(), data.data(), data.size());
 }
diff --git a/common/test_utils.h b/common/test_utils.h
index ddb3d34..ffe6f67 100644
--- a/common/test_utils.h
+++ b/common/test_utils.h
@@ -95,11 +95,6 @@
 // Reads a symlink from disk. Returns empty string on failure.
 std::string Readlink(const std::string& path);
 
-// Checks if xattr is supported in the directory specified by
-// |dir_path| which must be writable. Returns true if the feature is
-// supported, false if not or if an error occurred.
-bool IsXAttrSupported(const base::FilePath& dir_path);
-
 void FillWithData(brillo::Blob* buffer);
 
 // Compare the value of native array for download source parameter.
diff --git a/common/utils.cc b/common/utils.cc
index d935535..1a8fd53 100644
--- a/common/utils.cc
+++ b/common/utils.cc
@@ -61,6 +61,7 @@
 using base::Time;
 using base::TimeDelta;
 using std::min;
+using std::numeric_limits;
 using std::pair;
 using std::string;
 using std::vector;
@@ -1070,6 +1071,48 @@
   return true;
 }
 
+int VersionPrefix(const std::string& version) {
+  if (version.empty()) {
+    return 0;
+  }
+  vector<string> tokens = base::SplitString(
+      version, ".", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
+  int value;
+  if (tokens.empty() || !base::StringToInt(tokens[0], &value))
+    return -1;  // Target version is invalid.
+  return value;
+}
+
+void ParseRollbackKeyVersion(const string& raw_version,
+                             uint16_t* high_version,
+                             uint16_t* low_version) {
+  DCHECK(high_version);
+  DCHECK(low_version);
+  *high_version = numeric_limits<uint16_t>::max();
+  *low_version = numeric_limits<uint16_t>::max();
+
+  vector<string> parts = base::SplitString(
+      raw_version, ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
+  if (parts.size() != 2) {
+    // The version string must have exactly one period.
+    return;
+  }
+
+  int high;
+  int low;
+  if (!(base::StringToInt(parts[0], &high) &&
+        base::StringToInt(parts[1], &low))) {
+    // Both parts of the version could not be parsed correctly.
+    return;
+  }
+
+  if (high >= 0 && high < numeric_limits<uint16_t>::max() && low >= 0 &&
+      low < numeric_limits<uint16_t>::max()) {
+    *high_version = static_cast<uint16_t>(high);
+    *low_version = static_cast<uint16_t>(low);
+  }
+}
+
 }  // namespace utils
 
 }  // namespace chromeos_update_engine
diff --git a/common/utils.h b/common/utils.h
index 83a63ef..f7f285b 100644
--- a/common/utils.h
+++ b/common/utils.h
@@ -21,6 +21,7 @@
 #include <unistd.h>
 
 #include <algorithm>
+#include <limits>
 #include <map>
 #include <memory>
 #include <set>
@@ -327,6 +328,18 @@
   return DivRoundUp(x, y) * y;
 }
 
+// Returns the integer value of the first section of |version|. E.g. for
+//  "10575.39." returns 10575. Returns 0 if |version| is empty, returns -1 if
+// first section of |version| is invalid (e.g. not a number).
+int VersionPrefix(const std::string& version);
+
+// Parses a string in the form high.low, where high and low are 16 bit unsigned
+// integers. If there is more than 1 dot, or if either of the two parts are
+// not valid 16 bit unsigned numbers, then 0xffff is returned for both.
+void ParseRollbackKeyVersion(const std::string& raw_version,
+                             uint16_t* high_version,
+                             uint16_t* low_version);
+
 }  // namespace utils
 
 
diff --git a/common/utils_unittest.cc b/common/utils_unittest.cc
index dff0569..3405b68 100644
--- a/common/utils_unittest.cc
+++ b/common/utils_unittest.cc
@@ -22,6 +22,7 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 
+#include <limits>
 #include <string>
 #include <vector>
 
@@ -32,6 +33,7 @@
 
 #include "update_engine/common/test_utils.h"
 
+using std::numeric_limits;
 using std::string;
 using std::vector;
 
@@ -446,6 +448,22 @@
   *ret = true;
 }
 
+static void ExpectParseRollbackKeyVersion(const string& version,
+                                          uint16_t expected_high,
+                                          uint16_t expected_low) {
+  uint16_t actual_high;
+  uint16_t actual_low;
+  utils::ParseRollbackKeyVersion(version, &actual_high, &actual_low);
+  EXPECT_EQ(expected_high, actual_high);
+  EXPECT_EQ(expected_low, actual_low);
+}
+
+static void ExpectInvalidParseRollbackKeyVersion(const string& version) {
+  ExpectParseRollbackKeyVersion(version,
+                                numeric_limits<uint16_t>::max(),
+                                numeric_limits<uint16_t>::max());
+}
+
 TEST(UtilsTest, TestMacros) {
   bool void_test = false;
   VoidMacroTestHelper(&void_test);
@@ -503,4 +521,41 @@
   EXPECT_FALSE(utils::IsMountpoint(file.path()));
 }
 
+TEST(UtilsTest, VersionPrefix) {
+  EXPECT_EQ(10575, utils::VersionPrefix("10575.39."));
+  EXPECT_EQ(10575, utils::VersionPrefix("10575.39"));
+  EXPECT_EQ(10575, utils::VersionPrefix("10575.x"));
+  EXPECT_EQ(10575, utils::VersionPrefix("10575."));
+  EXPECT_EQ(10575, utils::VersionPrefix("10575"));
+  EXPECT_EQ(0, utils::VersionPrefix(""));
+  EXPECT_EQ(-1, utils::VersionPrefix("x"));
+  EXPECT_EQ(-1, utils::VersionPrefix("1x"));
+  EXPECT_EQ(-1, utils::VersionPrefix("x.1"));
+}
+
+TEST(UtilsTest, ParseDottedVersion) {
+  // Valid case.
+  ExpectParseRollbackKeyVersion("2.3", 2, 3);
+  ExpectParseRollbackKeyVersion("65535.65535", 65535, 65535);
+
+  // Zero is technically allowed but never actually used.
+  ExpectParseRollbackKeyVersion("0.0", 0, 0);
+
+  // Invalid cases.
+  ExpectInvalidParseRollbackKeyVersion("");
+  ExpectInvalidParseRollbackKeyVersion("2");
+  ExpectInvalidParseRollbackKeyVersion("2.");
+  ExpectInvalidParseRollbackKeyVersion(".2");
+  ExpectInvalidParseRollbackKeyVersion("2.2.");
+  ExpectInvalidParseRollbackKeyVersion("2.2.3");
+  ExpectInvalidParseRollbackKeyVersion(".2.2");
+  ExpectInvalidParseRollbackKeyVersion("a.b");
+  ExpectInvalidParseRollbackKeyVersion("1.b");
+  ExpectInvalidParseRollbackKeyVersion("a.2");
+  ExpectInvalidParseRollbackKeyVersion("65536.65536");
+  ExpectInvalidParseRollbackKeyVersion("99999.99999");
+  ExpectInvalidParseRollbackKeyVersion("99999.1");
+  ExpectInvalidParseRollbackKeyVersion("1.99999");
+}
+
 }  // namespace chromeos_update_engine
diff --git a/common_service.cc b/common_service.cc
index 2c8f80d..2fdd700 100644
--- a/common_service.cc
+++ b/common_service.cc
@@ -16,7 +16,6 @@
 
 #include "update_engine/common_service.h"
 
-#include <set>
 #include <string>
 
 #include <base/bind.h>
@@ -41,7 +40,6 @@
 using base::StringPrintf;
 using brillo::ErrorPtr;
 using brillo::string_utils::ToString;
-using std::set;
 using std::string;
 using update_engine::UpdateAttemptFlags;
 using update_engine::UpdateEngineStatus;
@@ -258,22 +256,11 @@
 
 bool UpdateEngineService::SetUpdateOverCellularPermission(ErrorPtr* error,
                                                           bool in_allowed) {
-  set<string> allowed_types;
-  const policy::DevicePolicy* device_policy = system_state_->device_policy();
-
-  // The device_policy is loaded in a lazy way before an update check. Load it
-  // now from the libbrillo cache if it wasn't already loaded.
-  if (!device_policy) {
-    UpdateAttempter* update_attempter = system_state_->update_attempter();
-    if (update_attempter) {
-      update_attempter->RefreshDevicePolicy();
-      device_policy = system_state_->device_policy();
-    }
-  }
+  ConnectionManagerInterface* connection_manager =
+      system_state_->connection_manager();
 
   // Check if this setting is allowed by the device policy.
-  if (device_policy &&
-      device_policy->GetAllowedConnectionTypesForUpdate(&allowed_types)) {
+  if (connection_manager->IsAllowedConnectionTypesForUpdateSet()) {
     LogAndSetError(error,
                    FROM_HERE,
                    "Ignoring the update over cellular setting since there's "
@@ -286,7 +273,8 @@
 
   PrefsInterface* prefs = system_state_->prefs();
 
-  if (!prefs->SetBoolean(kPrefsUpdateOverCellularPermission, in_allowed)) {
+  if (!prefs ||
+      !prefs->SetBoolean(kPrefsUpdateOverCellularPermission, in_allowed)) {
     LogAndSetError(error,
                    FROM_HERE,
                    string("Error setting the update over cellular to ") +
@@ -296,24 +284,66 @@
   return true;
 }
 
-bool UpdateEngineService::GetUpdateOverCellularPermission(ErrorPtr* /* error */,
-                                                          bool* out_allowed) {
-  ConnectionManagerInterface* cm = system_state_->connection_manager();
+bool UpdateEngineService::SetUpdateOverCellularTarget(
+    brillo::ErrorPtr* error,
+    const std::string& target_version,
+    int64_t target_size) {
+  ConnectionManagerInterface* connection_manager =
+      system_state_->connection_manager();
 
-  // The device_policy is loaded in a lazy way before an update check and is
-  // used to determine if an update is allowed over cellular. Load the device
-  // policy now from the libbrillo cache if it wasn't already loaded.
-  if (!system_state_->device_policy()) {
-    UpdateAttempter* update_attempter = system_state_->update_attempter();
-    if (update_attempter)
-      update_attempter->RefreshDevicePolicy();
+  // Check if this setting is allowed by the device policy.
+  if (connection_manager->IsAllowedConnectionTypesForUpdateSet()) {
+    LogAndSetError(error,
+                   FROM_HERE,
+                   "Ignoring the update over cellular setting since there's "
+                   "a device policy enforcing this setting.");
+    return false;
   }
 
-  // Return the current setting based on the same logic used while checking for
-  // updates. A log message could be printed as the result of this test.
-  LOG(INFO) << "Checking if updates over cellular networks are allowed:";
-  *out_allowed = cm->IsUpdateAllowedOver(ConnectionType::kCellular,
-                                         ConnectionTethering::kUnknown);
+  // If the policy wasn't loaded yet, then it is still OK to change the local
+  // setting because the policy will be checked again during the update check.
+
+  PrefsInterface* prefs = system_state_->prefs();
+
+  if (!prefs ||
+      !prefs->SetString(kPrefsUpdateOverCellularTargetVersion,
+                        target_version) ||
+      !prefs->SetInt64(kPrefsUpdateOverCellularTargetSize, target_size)) {
+    LogAndSetError(
+        error, FROM_HERE, "Error setting the target for update over cellular.");
+    return false;
+  }
+  return true;
+}
+
+bool UpdateEngineService::GetUpdateOverCellularPermission(ErrorPtr* error,
+                                                          bool* out_allowed) {
+  ConnectionManagerInterface* connection_manager =
+      system_state_->connection_manager();
+
+  if (connection_manager->IsAllowedConnectionTypesForUpdateSet()) {
+    // We have device policy, so ignore the user preferences.
+    *out_allowed = connection_manager->IsUpdateAllowedOver(
+        ConnectionType::kCellular, ConnectionTethering::kUnknown);
+  } else {
+    PrefsInterface* prefs = system_state_->prefs();
+
+    if (!prefs || !prefs->Exists(kPrefsUpdateOverCellularPermission)) {
+      // Update is not allowed as user preference is not set or not available.
+      *out_allowed = false;
+      return true;
+    }
+
+    bool is_allowed;
+
+    if (!prefs->GetBoolean(kPrefsUpdateOverCellularPermission, &is_allowed)) {
+      LogAndSetError(error,
+                     FROM_HERE,
+                     "Error getting the update over cellular preference.");
+      return false;
+    }
+    *out_allowed = is_allowed;
+  }
   return true;
 }
 
diff --git a/common_service.h b/common_service.h
index 544dd93..824ef97 100644
--- a/common_service.h
+++ b/common_service.h
@@ -114,6 +114,12 @@
   bool SetUpdateOverCellularPermission(brillo::ErrorPtr* error,
                                        bool in_allowed);
 
+  // If there's no device policy installed, sets the update over cellular
+  // target. Otherwise, this method returns with an error.
+  bool SetUpdateOverCellularTarget(brillo::ErrorPtr* error,
+                                   const std::string& target_version,
+                                   int64_t target_size);
+
   // Returns the current value of the update over cellular network setting,
   // either forced by the device policy if the device is enrolled or the current
   // user preference otherwise.
diff --git a/connection_manager.cc b/connection_manager.cc
index be707bc..4063f24 100644
--- a/connection_manager.cc
+++ b/connection_manager.cc
@@ -31,6 +31,7 @@
 #include "update_engine/connection_utils.h"
 #include "update_engine/shill_proxy.h"
 #include "update_engine/system_state.h"
+#include "update_engine/update_attempter.h"
 
 using org::chromium::flimflam::ManagerProxyInterface;
 using org::chromium::flimflam::ServiceProxyInterface;
@@ -59,16 +60,27 @@
 
     case ConnectionType::kCellular: {
       set<string> allowed_types;
+
       const policy::DevicePolicy* device_policy =
           system_state_->device_policy();
 
-      // A device_policy is loaded in a lazy way right before an update check,
-      // so the device_policy should be already loaded at this point. If it's
-      // not, return a safe value for this setting.
+      // The device_policy is loaded in a lazy way before an update check. Load
+      // it now from the libbrillo cache if it wasn't already loaded.
       if (!device_policy) {
-        LOG(INFO) << "Disabling updates over cellular networks as there's no "
-                     "device policy loaded yet.";
-        return false;
+        UpdateAttempter* update_attempter = system_state_->update_attempter();
+        if (update_attempter) {
+          update_attempter->RefreshDevicePolicy();
+          device_policy = system_state_->device_policy();
+        }
+      }
+
+      if (!device_policy) {
+        // Device policy fails to be loaded (possibly due to guest account). We
+        // do not check the local user setting here, which should be checked by
+        // |OmahaRequestAction| during checking for update.
+        LOG(INFO) << "Allowing updates over cellular as device policy "
+                     "fails to be loaded.";
+        return true;
       }
 
       if (device_policy->GetAllowedConnectionTypesForUpdate(&allowed_types)) {
@@ -82,31 +94,14 @@
 
         LOG(INFO) << "Allowing updates over cellular per device policy.";
         return true;
-      } else {
-        // There's no update setting in the device policy, using the local user
-        // setting.
-        PrefsInterface* prefs = system_state_->prefs();
-
-        if (!prefs || !prefs->Exists(kPrefsUpdateOverCellularPermission)) {
-          LOG(INFO) << "Disabling updates over cellular connection as there's "
-                       "no device policy setting nor user preference present.";
-          return false;
-        }
-
-        bool stored_value;
-        if (!prefs->GetBoolean(kPrefsUpdateOverCellularPermission,
-                               &stored_value)) {
-          return false;
-        }
-
-        if (!stored_value) {
-          LOG(INFO) << "Disabling updates over cellular connection per user "
-                       "setting.";
-          return false;
-        }
-        LOG(INFO) << "Allowing updates over cellular per user setting.";
-        return true;
       }
+
+      // If there's no update setting in the device policy, we do not check
+      // the local user setting here, which should be checked by
+      // |OmahaRequestAction| during checking for update.
+      LOG(INFO) << "Allowing updates over cellular as device policy does "
+                   "not include update setting.";
+      return true;
     }
 
     default:
@@ -121,6 +116,21 @@
   }
 }
 
+bool ConnectionManager::IsAllowedConnectionTypesForUpdateSet() const {
+  const policy::DevicePolicy* device_policy = system_state_->device_policy();
+  if (!device_policy) {
+    LOG(INFO) << "There's no device policy loaded yet.";
+    return false;
+  }
+
+  set<string> allowed_types;
+  if (!device_policy->GetAllowedConnectionTypesForUpdate(&allowed_types)) {
+    return false;
+  }
+
+  return true;
+}
+
 bool ConnectionManager::GetConnectionProperties(
     ConnectionType* out_type, ConnectionTethering* out_tethering) {
   dbus::ObjectPath default_service_path;
diff --git a/connection_manager.h b/connection_manager.h
index e5a9d49..dc563ef 100644
--- a/connection_manager.h
+++ b/connection_manager.h
@@ -17,6 +17,7 @@
 #ifndef UPDATE_ENGINE_CONNECTION_MANAGER_H_
 #define UPDATE_ENGINE_CONNECTION_MANAGER_H_
 
+#include <memory>
 #include <string>
 
 #include <base/macros.h>
@@ -43,6 +44,7 @@
                                ConnectionTethering* out_tethering) override;
   bool IsUpdateAllowedOver(ConnectionType type,
                            ConnectionTethering tethering) const override;
+  bool IsAllowedConnectionTypesForUpdateSet() const override;
 
  private:
   // Returns (via out_path) the default network path, or empty string if
diff --git a/connection_manager_android.cc b/connection_manager_android.cc
index 2dd824a..6da4cee 100644
--- a/connection_manager_android.cc
+++ b/connection_manager_android.cc
@@ -16,6 +16,8 @@
 
 #include "update_engine/connection_manager_android.h"
 
+#include <memory>
+
 namespace chromeos_update_engine {
 
 namespace connection_manager {
@@ -34,5 +36,8 @@
     ConnectionType type, ConnectionTethering tethering) const {
   return true;
 }
+bool ConnectionManagerAndroid::IsAllowedConnectionTypesForUpdateSet() const {
+  return false;
+}
 
 }  // namespace chromeos_update_engine
diff --git a/connection_manager_android.h b/connection_manager_android.h
index 0cd5e73..006f4ea 100644
--- a/connection_manager_android.h
+++ b/connection_manager_android.h
@@ -34,6 +34,7 @@
                                ConnectionTethering* out_tethering) override;
   bool IsUpdateAllowedOver(ConnectionType type,
                            ConnectionTethering tethering) const override;
+  bool IsAllowedConnectionTypesForUpdateSet() const override;
 
   DISALLOW_COPY_AND_ASSIGN(ConnectionManagerAndroid);
 };
diff --git a/connection_manager_interface.h b/connection_manager_interface.h
index df8eb4b..2faeb80 100644
--- a/connection_manager_interface.h
+++ b/connection_manager_interface.h
@@ -46,6 +46,10 @@
   virtual bool IsUpdateAllowedOver(ConnectionType type,
                                    ConnectionTethering tethering) const = 0;
 
+  // Returns true if the allowed connection types for update is set in the
+  // device policy. Otherwise, returns false.
+  virtual bool IsAllowedConnectionTypesForUpdateSet() const = 0;
+
  protected:
   ConnectionManagerInterface() = default;
 
diff --git a/connection_manager_unittest.cc b/connection_manager_unittest.cc
index e26a686..85b8c57 100644
--- a/connection_manager_unittest.cc
+++ b/connection_manager_unittest.cc
@@ -16,8 +16,10 @@
 
 #include "update_engine/connection_manager.h"
 
+#include <memory>
 #include <set>
 #include <string>
+#include <utility>
 
 #include <base/logging.h>
 #include <brillo/any.h>
@@ -276,16 +278,24 @@
                                         ConnectionTethering::kConfirmed));
 }
 
-TEST_F(ConnectionManagerTest, BlockUpdatesOverCellularByDefaultTest) {
-  EXPECT_FALSE(cmut_.IsUpdateAllowedOver(ConnectionType::kCellular,
-                                         ConnectionTethering::kUnknown));
+TEST_F(ConnectionManagerTest, AllowUpdatesOverCellularByDefaultTest) {
+  policy::MockDevicePolicy device_policy;
+  // Set an empty device policy.
+  fake_system_state_.set_device_policy(&device_policy);
+
+  EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kCellular,
+                                        ConnectionTethering::kUnknown));
 }
 
-TEST_F(ConnectionManagerTest, BlockUpdatesOverTetheredNetworkByDefaultTest) {
-  EXPECT_FALSE(cmut_.IsUpdateAllowedOver(ConnectionType::kWifi,
-                                         ConnectionTethering::kConfirmed));
-  EXPECT_FALSE(cmut_.IsUpdateAllowedOver(ConnectionType::kEthernet,
-                                         ConnectionTethering::kConfirmed));
+TEST_F(ConnectionManagerTest, AllowUpdatesOverTetheredNetworkByDefaultTest) {
+  policy::MockDevicePolicy device_policy;
+  // Set an empty device policy.
+  fake_system_state_.set_device_policy(&device_policy);
+
+  EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kWifi,
+                                        ConnectionTethering::kConfirmed));
+  EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kEthernet,
+                                        ConnectionTethering::kConfirmed));
   EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kWifi,
                                         ConnectionTethering::kSuspected));
 }
@@ -310,62 +320,27 @@
                                          ConnectionTethering::kUnknown));
 }
 
-TEST_F(ConnectionManagerTest, BlockUpdatesOver3GIfErrorInPolicyFetchTest) {
-  policy::MockDevicePolicy allow_3g_policy;
+TEST_F(ConnectionManagerTest, AllowUpdatesOver3GIfPolicyIsNotSet) {
+  policy::MockDevicePolicy device_policy;
 
-  fake_system_state_.set_device_policy(&allow_3g_policy);
-
-  set<string> allowed_set;
-  allowed_set.insert(StringForConnectionType(ConnectionType::kCellular));
+  fake_system_state_.set_device_policy(&device_policy);
 
   // Return false for GetAllowedConnectionTypesForUpdate and see
-  // that updates are still blocked for 3G despite the value being in
-  // the string set above.
-  EXPECT_CALL(allow_3g_policy, GetAllowedConnectionTypesForUpdate(_))
-      .Times(1)
-      .WillOnce(DoAll(SetArgPointee<0>(allowed_set), Return(false)));
-
-  EXPECT_FALSE(cmut_.IsUpdateAllowedOver(ConnectionType::kCellular,
-                                         ConnectionTethering::kUnknown));
-}
-
-TEST_F(ConnectionManagerTest, UseUserPrefForUpdatesOverCellularIfNoPolicyTest) {
-  policy::MockDevicePolicy no_policy;
-  testing::NiceMock<MockPrefs>* prefs = fake_system_state_.mock_prefs();
-
-  fake_system_state_.set_device_policy(&no_policy);
-
-  // No setting enforced by the device policy, user prefs should be used.
-  EXPECT_CALL(no_policy, GetAllowedConnectionTypesForUpdate(_))
-      .Times(3)
-      .WillRepeatedly(Return(false));
-
-  // No user pref: block.
-  EXPECT_CALL(*prefs, Exists(kPrefsUpdateOverCellularPermission))
+  // that updates are allowed as device policy is not set. Further
+  // check is left to |OmahaRequestAction|.
+  EXPECT_CALL(device_policy, GetAllowedConnectionTypesForUpdate(_))
       .Times(1)
       .WillOnce(Return(false));
-  EXPECT_FALSE(cmut_.IsUpdateAllowedOver(ConnectionType::kCellular,
-                                         ConnectionTethering::kUnknown));
 
-  // Allow per user pref.
-  EXPECT_CALL(*prefs, Exists(kPrefsUpdateOverCellularPermission))
-      .Times(1)
-      .WillOnce(Return(true));
-  EXPECT_CALL(*prefs, GetBoolean(kPrefsUpdateOverCellularPermission, _))
-      .Times(1)
-      .WillOnce(DoAll(SetArgPointee<1>(true), Return(true)));
   EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kCellular,
                                         ConnectionTethering::kUnknown));
+}
 
-  // Block per user pref.
-  EXPECT_CALL(*prefs, Exists(kPrefsUpdateOverCellularPermission))
-      .Times(1)
-      .WillOnce(Return(true));
-  EXPECT_CALL(*prefs, GetBoolean(kPrefsUpdateOverCellularPermission, _))
-      .Times(1)
-      .WillOnce(DoAll(SetArgPointee<1>(false), Return(true)));
-  EXPECT_FALSE(cmut_.IsUpdateAllowedOver(ConnectionType::kCellular,
-                                         ConnectionTethering::kUnknown));
+TEST_F(ConnectionManagerTest, AllowUpdatesOverCellularIfPolicyFailsToBeLoaded) {
+  fake_system_state_.set_device_policy(nullptr);
+
+  EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kCellular,
+                                        ConnectionTethering::kUnknown));
 }
 
 TEST_F(ConnectionManagerTest, StringForConnectionTypeTest) {
diff --git a/dbus_bindings/org.chromium.KioskAppService.dbus-xml b/dbus_bindings/org.chromium.KioskAppService.dbus-xml
new file mode 100644
index 0000000..11b888b
--- /dev/null
+++ b/dbus_bindings/org.chromium.KioskAppService.dbus-xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<node name="/org/chromium/KioskAppService"
+      xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+  <interface name="org.chromium.KioskAppServiceInterface">
+    <method name="GetRequiredPlatformVersion">
+      <arg name="required_platform_version" type="s" direction="out" />
+    </method>
+  </interface>
+</node>
diff --git a/dbus_bindings/org.chromium.LibCrosService.dbus-xml b/dbus_bindings/org.chromium.LibCrosService.dbus-xml
deleted file mode 100644
index 3111c63..0000000
--- a/dbus_bindings/org.chromium.LibCrosService.dbus-xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8" ?>
-
-<node name="/org/chromium/LibCrosService"
-      xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
-  <interface name="org.chromium.LibCrosServiceInterface">
-    <method name="GetKioskAppRequiredPlatformVersion">
-      <arg name="required_platform_version" type="s" direction="out" />
-    </method>
-  </interface>
-</node>
diff --git a/dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml b/dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml
index 848f775..a20f33f 100644
--- a/dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml
+++ b/dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml
@@ -67,6 +67,10 @@
     <method name="SetUpdateOverCellularPermission">
       <arg type="b" name="allowed" direction="in" />
     </method>
+    <method name="SetUpdateOverCellularTarget">
+      <arg type="s" name="target_version" direction="in" />
+      <arg type="x" name="target_size" direction="in" />
+    </method>
     <method name="GetUpdateOverCellularPermission">
       <arg type="b" name="allowed" direction="out" />
     </method>
diff --git a/dbus_service.cc b/dbus_service.cc
index 47aeec7..c7bc9f0 100644
--- a/dbus_service.cc
+++ b/dbus_service.cc
@@ -50,14 +50,11 @@
   bool interactive = !(flags & update_engine::kAttemptUpdateFlagNonInteractive);
   bool result;
   return common_->AttemptUpdate(
-             error,
-             in_app_version,
-             in_omaha_url,
-             interactive
-                 ? 0
-                 : update_engine::UpdateAttemptFlags::kFlagNonInteractive,
-             &result) &&
-         result;
+      error,
+      in_app_version,
+      in_omaha_url,
+      interactive ? 0 : update_engine::UpdateAttemptFlags::kFlagNonInteractive,
+      &result);
 }
 
 bool DBusUpdateEngineService::AttemptRollback(ErrorPtr* error,
@@ -133,6 +130,14 @@
   return common_->SetUpdateOverCellularPermission(error, in_allowed);
 }
 
+bool DBusUpdateEngineService::SetUpdateOverCellularTarget(
+    brillo::ErrorPtr* error,
+    const std::string& target_version,
+    int64_t target_size) {
+  return common_->SetUpdateOverCellularTarget(
+      error, target_version, target_size);
+}
+
 bool DBusUpdateEngineService::GetUpdateOverCellularPermission(
     ErrorPtr* error, bool* out_allowed) {
   return common_->GetUpdateOverCellularPermission(error, out_allowed);
diff --git a/dbus_service.h b/dbus_service.h
index b754661..e461fa6 100644
--- a/dbus_service.h
+++ b/dbus_service.h
@@ -114,6 +114,12 @@
   bool SetUpdateOverCellularPermission(brillo::ErrorPtr* error,
                                        bool in_allowed) override;
 
+  // If there's no device policy installed, sets the update over cellular
+  // target. Otherwise, this method returns with an error.
+  bool SetUpdateOverCellularTarget(brillo::ErrorPtr* error,
+                                   const std::string& target_version,
+                                   int64_t target_size) override;
+
   // Returns the current value of the update over cellular network setting,
   // either forced by the device policy if the device is enrolled or the current
   // user preference otherwise.
@@ -154,7 +160,7 @@
 class UpdateEngineAdaptor : public org::chromium::UpdateEngineInterfaceAdaptor,
                             public ServiceObserverInterface {
  public:
-  UpdateEngineAdaptor(SystemState* system_state);
+  explicit UpdateEngineAdaptor(SystemState* system_state);
   ~UpdateEngineAdaptor() = default;
 
   // Register the DBus object with the update engine service asynchronously.
diff --git a/hardware_android.cc b/hardware_android.cc
index cc052b2..9dd8bb6 100644
--- a/hardware_android.cc
+++ b/hardware_android.cc
@@ -166,6 +166,32 @@
   return GetProperty(kPropBootBaseband, "");
 }
 
+int HardwareAndroid::GetMinKernelKeyVersion() const {
+  LOG(WARNING) << "STUB: No Kernel key version is available.";
+  return -1;
+}
+
+int HardwareAndroid::GetMinFirmwareKeyVersion() const {
+  LOG(WARNING) << "STUB: No Firmware key version is available.";
+  return -1;
+}
+
+int HardwareAndroid::GetMaxFirmwareKeyRollforward() const {
+  LOG(WARNING) << "STUB: Getting firmware_max_rollforward is not supported.";
+  return -1;
+}
+
+bool HardwareAndroid::SetMaxFirmwareKeyRollforward(
+    int firmware_max_rollforward) {
+  LOG(WARNING) << "STUB: Setting firmware_max_rollforward is not supported.";
+  return false;
+}
+
+bool HardwareAndroid::SetMaxKernelKeyRollforward(int kernel_max_rollforward) {
+  LOG(WARNING) << "STUB: Setting kernel_max_rollforward is not supported.";
+  return false;
+}
+
 int HardwareAndroid::GetPowerwashCount() const {
   LOG(WARNING) << "STUB: Assuming no factory reset was performed.";
   return 0;
@@ -204,9 +230,10 @@
   return false;
 }
 
-void HardwareAndroid::SetFirstActiveOmahaPingSent() {
-  LOG(WARNING) << "STUB: Assuming first active omaha is never set.";
-  return;
+bool HardwareAndroid::SetFirstActiveOmahaPingSent() {
+  LOG(WARNING) << "STUB: Assuming first active omaha is set.";
+  // We will set it true, so its failure doesn't cause escalation.
+  return true;
 }
 
 }  // namespace chromeos_update_engine
diff --git a/hardware_android.h b/hardware_android.h
index ca90b62..920b659 100644
--- a/hardware_android.h
+++ b/hardware_android.h
@@ -42,6 +42,11 @@
   std::string GetHardwareClass() const override;
   std::string GetFirmwareVersion() const override;
   std::string GetECVersion() const override;
+  int GetMinKernelKeyVersion() const override;
+  int GetMinFirmwareKeyVersion() const override;
+  int GetMaxFirmwareKeyRollforward() const override;
+  bool SetMaxFirmwareKeyRollforward(int firmware_max_rollforward) override;
+  bool SetMaxKernelKeyRollforward(int kernel_max_rollforward) override;
   int GetPowerwashCount() const override;
   bool SchedulePowerwash() override;
   bool CancelPowerwash() override;
@@ -49,7 +54,7 @@
   bool GetPowerwashSafeDirectory(base::FilePath* path) const override;
   int64_t GetBuildTimestamp() const override;
   bool GetFirstActiveOmahaPingSent() const override;
-  void SetFirstActiveOmahaPingSent() override;
+  bool SetFirstActiveOmahaPingSent() override;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(HardwareAndroid);
diff --git a/hardware_chromeos.cc b/hardware_chromeos.cc
index f2bb28a..3949328 100644
--- a/hardware_chromeos.cc
+++ b/hardware_chromeos.cc
@@ -16,6 +16,8 @@
 
 #include "update_engine/hardware_chromeos.h"
 
+#include <utility>
+
 #include <base/files/file_path.h>
 #include <base/files/file_util.h>
 #include <base/logging.h>
@@ -180,6 +182,34 @@
   return utils::ParseECVersion(input_line);
 }
 
+int HardwareChromeOS::GetMinKernelKeyVersion() const {
+  return VbGetSystemPropertyInt("tpm_kernver");
+}
+
+int HardwareChromeOS::GetMaxFirmwareKeyRollforward() const {
+  return VbGetSystemPropertyInt("firmware_max_rollforward");
+}
+
+bool HardwareChromeOS::SetMaxFirmwareKeyRollforward(
+    int firmware_max_rollforward) {
+  // Not all devices have this field yet. So first try to read
+  // it and if there is an error just fail.
+  if (GetMaxFirmwareKeyRollforward() == -1)
+    return false;
+
+  return VbSetSystemPropertyInt("firmware_max_rollforward",
+                                firmware_max_rollforward) == 0;
+}
+
+int HardwareChromeOS::GetMinFirmwareKeyVersion() const {
+  return VbGetSystemPropertyInt("tpm_fwver");
+}
+
+bool HardwareChromeOS::SetMaxKernelKeyRollforward(int kernel_max_rollforward) {
+  return VbSetSystemPropertyInt("kernel_max_rollforward",
+                                kernel_max_rollforward) == 0;
+}
+
 int HardwareChromeOS::GetPowerwashCount() const {
   int powerwash_count;
   base::FilePath marker_path = base::FilePath(kPowerwashSafeDirectory).Append(
@@ -277,7 +307,7 @@
   return static_cast<bool>(active_ping);
 }
 
-void HardwareChromeOS::SetFirstActiveOmahaPingSent() {
+bool HardwareChromeOS::SetFirstActiveOmahaPingSent() {
   int exit_code = 0;
   string output;
   vector<string> vpd_set_cmd = {
@@ -287,7 +317,7 @@
     LOG(ERROR) << "Failed to set vpd key for " << kActivePingKey
                << " with exit code: " << exit_code
                << " with error: " << output;
-    return;
+    return false;
   }
 
   vector<string> vpd_dump_cmd = { "dump_vpd_log", "--force" };
@@ -296,7 +326,9 @@
     LOG(ERROR) << "Failed to cache " << kActivePingKey<< " using dump_vpd_log"
                << " with exit code: " << exit_code
                << " with error: " << output;
+    return false;
   }
+  return true;
 }
 
 }  // namespace chromeos_update_engine
diff --git a/hardware_chromeos.h b/hardware_chromeos.h
index 0cf1214..5c66641 100644
--- a/hardware_chromeos.h
+++ b/hardware_chromeos.h
@@ -17,6 +17,7 @@
 #ifndef UPDATE_ENGINE_HARDWARE_CHROMEOS_H_
 #define UPDATE_ENGINE_HARDWARE_CHROMEOS_H_
 
+#include <memory>
 #include <string>
 #include <vector>
 
@@ -46,6 +47,11 @@
   std::string GetHardwareClass() const override;
   std::string GetFirmwareVersion() const override;
   std::string GetECVersion() const override;
+  int GetMinKernelKeyVersion() const override;
+  int GetMinFirmwareKeyVersion() const override;
+  int GetMaxFirmwareKeyRollforward() const override;
+  bool SetMaxFirmwareKeyRollforward(int firmware_max_rollforward) override;
+  bool SetMaxKernelKeyRollforward(int kernel_max_rollforward) override;
   int GetPowerwashCount() const override;
   bool SchedulePowerwash() override;
   bool CancelPowerwash() override;
@@ -53,7 +59,7 @@
   bool GetPowerwashSafeDirectory(base::FilePath* path) const override;
   int64_t GetBuildTimestamp() const override;
   bool GetFirstActiveOmahaPingSent() const override;
-  void SetFirstActiveOmahaPingSent() override;
+  bool SetFirstActiveOmahaPingSent() override;
 
  private:
   friend class HardwareChromeOSTest;
diff --git a/init/update-engine.conf b/init/update-engine.conf
index 4c05cf4..d3681db 100644
--- a/init/update-engine.conf
+++ b/init/update-engine.conf
@@ -22,7 +22,10 @@
 # also updating that reference.
 start on starting system-services
 stop on stopping system-services
-respawn
+# The default is 10 failures every 5 seconds, but even if we crash early, it is
+# hard to catch that. So here we set the crash rate as 10 failures every 20
+# seconds which will include the default and more.
+respawn limit 10 20
 
 expect fork
 
diff --git a/libcurl_http_fetcher.cc b/libcurl_http_fetcher.cc
index 87f30ad..50ddeb0 100644
--- a/libcurl_http_fetcher.cc
+++ b/libcurl_http_fetcher.cc
@@ -545,8 +545,8 @@
   }
   bytes_downloaded_ += payload_size;
   in_write_callback_ = true;
-  if (delegate_)
-    delegate_->ReceivedBytes(this, ptr, payload_size);
+  if (delegate_ && !delegate_->ReceivedBytes(this, ptr, payload_size))
+    return payload_size;
   in_write_callback_ = false;
   return payload_size;
 }
diff --git a/main.cc b/main.cc
index 5cf53ce..67a150e 100644
--- a/main.cc
+++ b/main.cc
@@ -194,6 +194,8 @@
   chromeos_update_engine::UpdateEngineDaemon update_engine_daemon;
   int exit_code = update_engine_daemon.Run();
 
+  chromeos_update_engine::Subprocess::Get().FlushBufferedLogsAtExit();
+
   LOG(INFO) << "A/B Update Engine terminating with exit code " << exit_code;
   return exit_code;
 }
diff --git a/metrics_reporter_android.h b/metrics_reporter_android.h
index a33e6f9..8a27ef6 100644
--- a/metrics_reporter_android.h
+++ b/metrics_reporter_android.h
@@ -17,6 +17,8 @@
 #ifndef UPDATE_ENGINE_METRICS_REPORTER_ANDROID_H_
 #define UPDATE_ENGINE_METRICS_REPORTER_ANDROID_H_
 
+#include <string>
+
 #include "update_engine/common/error_code.h"
 #include "update_engine/metrics_constants.h"
 #include "update_engine/metrics_reporter_interface.h"
@@ -33,6 +35,9 @@
 
   void ReportRollbackMetrics(metrics::RollbackResult result) override {}
 
+  void ReportEnterpriseRollbackMetrics(
+      bool success, const std::string& rollback_version) override {}
+
   void ReportDailyMetrics(base::TimeDelta os_age) override {}
 
   void ReportUpdateCheckMetrics(
@@ -80,6 +85,12 @@
 
   void ReportInstallDateProvisioningSource(int source, int max) override {}
 
+  void ReportInternalErrorCode(ErrorCode error_code) override {}
+
+  void ReportKeyVersionMetrics(int kernel_min_version,
+                               int kernel_max_rollforward_version,
+                               bool kernel_max_rollforward_success) override {}
+
  private:
   DISALLOW_COPY_AND_ASSIGN(MetricsReporterAndroid);
 };
diff --git a/metrics_reporter_interface.h b/metrics_reporter_interface.h
index e96ac1e..b677aaa 100644
--- a/metrics_reporter_interface.h
+++ b/metrics_reporter_interface.h
@@ -18,6 +18,7 @@
 #define UPDATE_ENGINE_METRICS_REPORTER_INTERFACE_H_
 
 #include <memory>
+#include <string>
 
 #include <base/time/time.h>
 
@@ -43,12 +44,20 @@
 
   virtual void Initialize() = 0;
 
-  // Helper function to report metrics related to rollback. The
+  // Helper function to report metrics related to user-initiated rollback. The
   // following metrics are reported:
   //
   //  |kMetricRollbackResult|
   virtual void ReportRollbackMetrics(metrics::RollbackResult result) = 0;
 
+  // Helper function to report metrics related to enterprise (admin-initiated)
+  // rollback:
+  //
+  //  |kMetricEnterpriseRollbackSuccess|
+  //  |kMetricEnterpriseRollbackFailure|
+  virtual void ReportEnterpriseRollbackMetrics(
+      bool success, const std::string& rollback_version) = 0;
+
   // Helper function to report metrics reported once a day. The
   // following metrics are reported:
   //
@@ -64,6 +73,8 @@
   //  |kMetricCheckDownloadErrorCode|
   //  |kMetricCheckTimeSinceLastCheckMinutes|
   //  |kMetricCheckTimeSinceLastCheckUptimeMinutes|
+  //  |kMetricCheckTargetVersion|
+  //  |kMetricCheckRollbackTargetVersion|
   //
   // The |kMetricCheckResult| metric will only be reported if |result|
   // is not |kUnset|.
@@ -78,6 +89,10 @@
   // |kMetricCheckTimeSinceLastCheckUptimeMinutes| metrics are
   // automatically reported and calculated by maintaining persistent
   // and process-local state variables.
+  //
+  // |kMetricCheckTargetVersion| reports the first section of the target version
+  // if it's set, |kMetricCheckRollbackTargetVersion| reports the same, but only
+  // if rollback is also allowed using enterprise policy.
   virtual void ReportUpdateCheckMetrics(
       SystemState* system_state,
       metrics::CheckResult result,
@@ -195,6 +210,22 @@
   //
   // |kMetricInstallDateProvisioningSource|
   virtual void ReportInstallDateProvisioningSource(int source, int max) = 0;
+
+  // Helper function to report an internal error code. The following metrics are
+  // reported:
+  //
+  // |kMetricAttemptInternalErrorCode|
+  virtual void ReportInternalErrorCode(ErrorCode error_code) = 0;
+
+  // Helper function to report metrics related to the verified boot key
+  // versions:
+  //
+  //  |kMetricKernelMinVersion|
+  //  |kMetricKernelMaxRollforwardVersion|
+  //  |kMetricKernelMaxRollforwardSetSuccess|
+  virtual void ReportKeyVersionMetrics(int kernel_min_version,
+                                       int kernel_max_rollforward_version,
+                                       bool kernel_max_rollforward_success) = 0;
 };
 
 }  // namespace chromeos_update_engine
diff --git a/metrics_reporter_omaha.cc b/metrics_reporter_omaha.cc
index df3e4d4..f0c6643 100644
--- a/metrics_reporter_omaha.cc
+++ b/metrics_reporter_omaha.cc
@@ -17,9 +17,9 @@
 #include "update_engine/metrics_reporter_omaha.h"
 
 #include <memory>
-#include <string>
 
 #include <base/logging.h>
+#include <base/strings/string_number_conversions.h>
 #include <metrics/metrics_library.h>
 
 #include "update_engine/common/clock_interface.h"
@@ -27,6 +27,7 @@
 #include "update_engine/common/prefs_interface.h"
 #include "update_engine/common/utils.h"
 #include "update_engine/metrics_utils.h"
+#include "update_engine/omaha_request_params.h"
 #include "update_engine/system_state.h"
 
 using std::string;
@@ -43,6 +44,9 @@
     "UpdateEngine.Check.DownloadErrorCode";
 const char kMetricCheckReaction[] = "UpdateEngine.Check.Reaction";
 const char kMetricCheckResult[] = "UpdateEngine.Check.Result";
+const char kMetricCheckTargetVersion[] = "UpdateEngine.Check.TargetVersion";
+const char kMetricCheckRollbackTargetVersion[] =
+    "UpdateEngine.Check.RollbackTargetVersion";
 const char kMetricCheckTimeSinceLastCheckMinutes[] =
     "UpdateEngine.Check.TimeSinceLastCheckMinutes";
 const char kMetricCheckTimeSinceLastCheckUptimeMinutes[] =
@@ -102,12 +106,25 @@
 // UpdateEngine.Rollback.* metric.
 const char kMetricRollbackResult[] = "UpdateEngine.Rollback.Result";
 
+// UpdateEngine.EnterpriseRollback.* metrics.
+const char kMetricEnterpriseRollbackFailure[] =
+    "UpdateEngine.EnterpriseRollback.Failure";
+const char kMetricEnterpriseRollbackSuccess[] =
+    "UpdateEngine.EnterpriseRollback.Success";
+
 // UpdateEngine.CertificateCheck.* metrics.
 const char kMetricCertificateCheckUpdateCheck[] =
     "UpdateEngine.CertificateCheck.UpdateCheck";
 const char kMetricCertificateCheckDownload[] =
     "UpdateEngine.CertificateCheck.Download";
 
+// UpdateEngine.KernelKey.* metrics.
+const char kMetricKernelMinVersion[] = "UpdateEngine.KernelKey.MinVersion";
+const char kMetricKernelMaxRollforwardVersion[] =
+    "UpdateEngine.KernelKey.MaxRollforwardVersion";
+const char kMetricKernelMaxRollforwardSetSuccess[] =
+    "UpdateEngine.KernelKey.MaxRollforwardSetSuccess";
+
 // UpdateEngine.* metrics.
 const char kMetricFailedUpdateCount[] = "UpdateEngine.FailedUpdateCount";
 const char kMetricInstallDateProvisioningSource[] =
@@ -196,6 +213,25 @@
                             30 * 24 * 60,  // max: 30 days
                             50);           // num_buckets
   }
+
+  // First section of target version specified for the update.
+  if (system_state && system_state->request_params()) {
+    string target_version =
+        system_state->request_params()->target_version_prefix();
+    value = utils::VersionPrefix(target_version);
+    if (value != 0) {
+      metric = metrics::kMetricCheckTargetVersion;
+      LOG(INFO) << "Sending " << value << " for metric " << metric
+                << " (sparse)";
+      metrics_lib_->SendSparseToUMA(metric, value);
+      if (system_state->request_params()->rollback_allowed()) {
+        metric = metrics::kMetricCheckRollbackTargetVersion;
+        LOG(INFO) << "Sending " << value << " for metric " << metric
+                  << " (sparse)";
+        metrics_lib_->SendSparseToUMA(metric, value);
+      }
+    }
+  }
 }
 
 void MetricsReporterOmaha::ReportAbnormallyTerminatedUpdateAttemptMetrics() {
@@ -271,12 +307,7 @@
       static_cast<int>(metrics::AttemptResult::kNumConstants));
 
   if (internal_error_code != ErrorCode::kSuccess) {
-    metric = metrics::kMetricAttemptInternalErrorCode;
-    LOG(INFO) << "Uploading " << internal_error_code << " for metric "
-              << metric;
-    metrics_lib_->SendEnumToUMA(metric,
-                                static_cast<int>(internal_error_code),
-                                static_cast<int>(ErrorCode::kUmaReportedMax));
+    ReportInternalErrorCode(internal_error_code);
   }
 
   base::TimeDelta time_since_last;
@@ -495,6 +526,16 @@
       metric, value, static_cast<int>(metrics::RollbackResult::kNumConstants));
 }
 
+void MetricsReporterOmaha::ReportEnterpriseRollbackMetrics(
+    bool success, const string& rollback_version) {
+  int value = utils::VersionPrefix(rollback_version);
+  string metric = metrics::kMetricEnterpriseRollbackSuccess;
+  if (!success)
+    metric = metrics::kMetricEnterpriseRollbackFailure;
+  LOG(INFO) << "Sending " << value << " for metric " << metric;
+  metrics_lib_->SendSparseToUMA(metric, value);
+}
+
 void MetricsReporterOmaha::ReportCertificateCheckMetrics(
     ServerToCheck server_to_check, CertificateCheckResult result) {
   string metric;
@@ -547,4 +588,33 @@
                               max);
 }
 
+void MetricsReporterOmaha::ReportInternalErrorCode(ErrorCode error_code) {
+  auto metric = metrics::kMetricAttemptInternalErrorCode;
+  LOG(INFO) << "Uploading " << error_code << " for metric " << metric;
+  metrics_lib_->SendEnumToUMA(metric,
+                              static_cast<int>(error_code),
+                              static_cast<int>(ErrorCode::kUmaReportedMax));
+}
+
+void MetricsReporterOmaha::ReportKeyVersionMetrics(
+    int kernel_min_version,
+    int kernel_max_rollforward_version,
+    bool kernel_max_rollforward_success) {
+  int value = kernel_min_version;
+  string metric = metrics::kMetricKernelMinVersion;
+  LOG(INFO) << "Sending " << value << " for metric " << metric;
+  metrics_lib_->SendSparseToUMA(metric, value);
+
+  value = kernel_max_rollforward_version;
+  metric = metrics::kMetricKernelMaxRollforwardVersion;
+  LOG(INFO) << "Sending " << value << " for metric " << metric;
+  metrics_lib_->SendSparseToUMA(metric, value);
+
+  bool bool_value = kernel_max_rollforward_success;
+  metric = metrics::kMetricKernelMaxRollforwardSetSuccess;
+  LOG(INFO) << "Sending " << bool_value << " for metric " << metric
+            << " (bool)";
+  metrics_lib_->SendBoolToUMA(metric, bool_value);
+}
+
 }  // namespace chromeos_update_engine
diff --git a/metrics_reporter_omaha.h b/metrics_reporter_omaha.h
index 5e88923..10aef86 100644
--- a/metrics_reporter_omaha.h
+++ b/metrics_reporter_omaha.h
@@ -18,6 +18,7 @@
 #define UPDATE_ENGINE_METRICS_REPORTER_OMAHA_H_
 
 #include <memory>
+#include <string>
 
 #include <base/time/time.h>
 #include <metrics/metrics_library.h>
@@ -42,6 +43,8 @@
 extern const char kMetricCheckDownloadErrorCode[];
 extern const char kMetricCheckReaction[];
 extern const char kMetricCheckResult[];
+extern const char kMetricCheckTargetVersion[];
+extern const char kMetricCheckRollbackTargetVersion[];
 extern const char kMetricCheckTimeSinceLastCheckMinutes[];
 extern const char kMetricCheckTimeSinceLastCheckUptimeMinutes[];
 
@@ -77,10 +80,19 @@
 // UpdateEngine.Rollback.* metric.
 extern const char kMetricRollbackResult[];
 
+// UpdateEngine.EnterpriseRollback.* metrics.
+extern const char kMetricEnterpriseRollbackFailure[];
+extern const char kMetricEnterpriseRollbackSuccess[];
+
 // UpdateEngine.CertificateCheck.* metrics.
 extern const char kMetricCertificateCheckUpdateCheck[];
 extern const char kMetricCertificateCheckDownload[];
 
+// UpdateEngine.KernelKey.* metrics.
+extern const char kMetricKernelMinVersion[];
+extern const char kMetricKernelMaxRollforwardVersion[];
+extern const char kMetricKernelMaxRollforwardSetSuccess[];
+
 // UpdateEngine.* metrics.
 extern const char kMetricFailedUpdateCount[];
 extern const char kMetricInstallDateProvisioningSource[];
@@ -98,6 +110,9 @@
 
   void ReportRollbackMetrics(metrics::RollbackResult result) override;
 
+  void ReportEnterpriseRollbackMetrics(
+      bool success, const std::string& rollback_version) override;
+
   void ReportDailyMetrics(base::TimeDelta os_age) override;
 
   void ReportUpdateCheckMetrics(
@@ -145,6 +160,12 @@
 
   void ReportInstallDateProvisioningSource(int source, int max) override;
 
+  void ReportInternalErrorCode(ErrorCode error_code) override;
+
+  void ReportKeyVersionMetrics(int kernel_min_version,
+                               int kernel_max_rollforward_version,
+                               bool kernel_max_rollforward_success) override;
+
  private:
   friend class MetricsReporterOmahaTest;
 
diff --git a/metrics_reporter_omaha_unittest.cc b/metrics_reporter_omaha_unittest.cc
index 2fe5d66..878a323 100644
--- a/metrics_reporter_omaha_unittest.cc
+++ b/metrics_reporter_omaha_unittest.cc
@@ -29,8 +29,9 @@
 #include "update_engine/fake_system_state.h"
 
 using base::TimeDelta;
-using testing::AnyNumber;
 using testing::_;
+using testing::AnyNumber;
+using testing::Return;
 
 namespace chromeos_update_engine {
 class MetricsReporterOmahaTest : public ::testing::Test {
@@ -85,6 +86,14 @@
                               static_cast<int>(error_code)))
       .Times(2);
 
+  // Not pinned nor rollback
+  EXPECT_CALL(*mock_metrics_lib_,
+              SendSparseToUMA(metrics::kMetricCheckTargetVersion, _))
+      .Times(0);
+  EXPECT_CALL(*mock_metrics_lib_,
+              SendSparseToUMA(metrics::kMetricCheckRollbackTargetVersion, _))
+      .Times(0);
+
   EXPECT_CALL(
       *mock_metrics_lib_,
       SendToUMA(metrics::kMetricCheckTimeSinceLastCheckMinutes, 1, _, _, _))
@@ -101,6 +110,62 @@
   // Advance the clock by 1 minute and report the same metrics again.
   fake_clock.SetWallclockTime(base::Time::FromInternalValue(61000000));
   fake_clock.SetMonotonicTime(base::Time::FromInternalValue(61000000));
+  // Allow rollback
+  reporter_.ReportUpdateCheckMetrics(
+      &fake_system_state, result, reaction, error_code);
+}
+
+TEST_F(MetricsReporterOmahaTest, ReportUpdateCheckMetricsPinned) {
+  FakeSystemState fake_system_state;
+
+  OmahaRequestParams params(&fake_system_state);
+  params.set_target_version_prefix("10575.");
+  params.set_rollback_allowed(false);
+  fake_system_state.set_request_params(&params);
+
+  metrics::CheckResult result = metrics::CheckResult::kUpdateAvailable;
+  metrics::CheckReaction reaction = metrics::CheckReaction::kIgnored;
+  metrics::DownloadErrorCode error_code =
+      metrics::DownloadErrorCode::kHttpStatus200;
+
+  EXPECT_CALL(*mock_metrics_lib_,
+              SendSparseToUMA(metrics::kMetricCheckDownloadErrorCode, _));
+  // Target version set, but not a rollback.
+  EXPECT_CALL(*mock_metrics_lib_,
+              SendSparseToUMA(metrics::kMetricCheckTargetVersion, 10575))
+      .Times(1);
+  EXPECT_CALL(*mock_metrics_lib_,
+              SendSparseToUMA(metrics::kMetricCheckRollbackTargetVersion, _))
+      .Times(0);
+
+  reporter_.ReportUpdateCheckMetrics(
+      &fake_system_state, result, reaction, error_code);
+}
+
+TEST_F(MetricsReporterOmahaTest, ReportUpdateCheckMetricsRollback) {
+  FakeSystemState fake_system_state;
+
+  OmahaRequestParams params(&fake_system_state);
+  params.set_target_version_prefix("10575.");
+  params.set_rollback_allowed(true);
+  fake_system_state.set_request_params(&params);
+
+  metrics::CheckResult result = metrics::CheckResult::kUpdateAvailable;
+  metrics::CheckReaction reaction = metrics::CheckReaction::kIgnored;
+  metrics::DownloadErrorCode error_code =
+      metrics::DownloadErrorCode::kHttpStatus200;
+
+  EXPECT_CALL(*mock_metrics_lib_,
+              SendSparseToUMA(metrics::kMetricCheckDownloadErrorCode, _));
+  // Rollback.
+  EXPECT_CALL(*mock_metrics_lib_,
+              SendSparseToUMA(metrics::kMetricCheckTargetVersion, 10575))
+      .Times(1);
+  EXPECT_CALL(
+      *mock_metrics_lib_,
+      SendSparseToUMA(metrics::kMetricCheckRollbackTargetVersion, 10575))
+      .Times(1);
+
   reporter_.ReportUpdateCheckMetrics(
       &fake_system_state, result, reaction, error_code);
 }
@@ -357,6 +422,18 @@
   reporter_.ReportRollbackMetrics(result);
 }
 
+TEST_F(MetricsReporterOmahaTest, ReportEnterpriseRollbackMetrics) {
+  EXPECT_CALL(*mock_metrics_lib_,
+              SendSparseToUMA(metrics::kMetricEnterpriseRollbackSuccess, 10575))
+      .Times(1);
+  EXPECT_CALL(*mock_metrics_lib_,
+              SendSparseToUMA(metrics::kMetricEnterpriseRollbackFailure, 10323))
+      .Times(1);
+
+  reporter_.ReportEnterpriseRollbackMetrics(/*success=*/true, "10575.39.2");
+  reporter_.ReportEnterpriseRollbackMetrics(/*success=*/false, "10323.67.7");
+}
+
 TEST_F(MetricsReporterOmahaTest, ReportCertificateCheckMetrics) {
   ServerToCheck server_to_check = ServerToCheck::kUpdate;
   CertificateCheckResult result = CertificateCheckResult::kValid;
@@ -401,4 +478,26 @@
   reporter_.ReportInstallDateProvisioningSource(source, max);
 }
 
+TEST_F(MetricsReporterOmahaTest, ReportKeyVersionMetrics) {
+  int kernel_min_version = 0x00040002;
+  int kernel_max_rollforward_version = 0xfffffffe;
+  bool kernel_max_rollforward_success = true;
+  EXPECT_CALL(
+      *mock_metrics_lib_,
+      SendSparseToUMA(metrics::kMetricKernelMinVersion, kernel_min_version))
+      .Times(1);
+  EXPECT_CALL(*mock_metrics_lib_,
+              SendSparseToUMA(metrics::kMetricKernelMaxRollforwardVersion,
+                              kernel_max_rollforward_version))
+      .Times(1);
+  EXPECT_CALL(*mock_metrics_lib_,
+              SendBoolToUMA(metrics::kMetricKernelMaxRollforwardSetSuccess,
+                            kernel_max_rollforward_success))
+      .Times(1);
+
+  reporter_.ReportKeyVersionMetrics(kernel_min_version,
+                                    kernel_max_rollforward_version,
+                                    kernel_max_rollforward_success);
+}
+
 }  // namespace chromeos_update_engine
diff --git a/metrics_reporter_stub.h b/metrics_reporter_stub.h
index 6ffd05c..87023ee 100644
--- a/metrics_reporter_stub.h
+++ b/metrics_reporter_stub.h
@@ -17,6 +17,8 @@
 #ifndef UPDATE_ENGINE_METRICS_REPORTER_STUB_H_
 #define UPDATE_ENGINE_METRICS_REPORTER_STUB_H_
 
+#include <string>
+
 #include "update_engine/common/error_code.h"
 #include "update_engine/metrics_constants.h"
 #include "update_engine/metrics_reporter_interface.h"
@@ -33,6 +35,9 @@
 
   void ReportRollbackMetrics(metrics::RollbackResult result) override {}
 
+  void ReportEnterpriseRollbackMetrics(
+      bool success, const std::string& rollback_version) override {}
+
   void ReportDailyMetrics(base::TimeDelta os_age) override {}
 
   void ReportUpdateCheckMetrics(
@@ -80,6 +85,12 @@
 
   void ReportInstallDateProvisioningSource(int source, int max) override {}
 
+  void ReportInternalErrorCode(ErrorCode error_code) override {}
+
+  void ReportKeyVersionMetrics(int kernel_min_version,
+                               int kernel_max_rollforward_version,
+                               bool kernel_max_rollforward_success) override {}
+
  private:
   DISALLOW_COPY_AND_ASSIGN(MetricsReporterStub);
 };
diff --git a/metrics_utils.cc b/metrics_utils.cc
index 40deda8..d80d394 100644
--- a/metrics_utils.cc
+++ b/metrics_utils.cc
@@ -83,6 +83,7 @@
 
     case ErrorCode::kNewRootfsVerificationError:
     case ErrorCode::kNewKernelVerificationError:
+    case ErrorCode::kRollbackNotPossible:
       return metrics::AttemptResult::kVerificationFailed;
 
     case ErrorCode::kPostinstallRunnerError:
@@ -114,7 +115,9 @@
     case ErrorCode::kPostinstallPowerwashError:
     case ErrorCode::kUpdateCanceledByChannelChange:
     case ErrorCode::kOmahaRequestXMLHasEntityDecl:
+    case ErrorCode::kOmahaUpdateIgnoredOverCellular:
     case ErrorCode::kNoUpdate:
+    case ErrorCode::kFirstActiveOmahaPingSentPersistenceError:
       return metrics::AttemptResult::kInternalError;
 
     // Special flags. These can't happen (we mask them out above) but
@@ -215,9 +218,12 @@
     case ErrorCode::kOmahaRequestXMLHasEntityDecl:
     case ErrorCode::kFilesystemVerifierError:
     case ErrorCode::kUserCanceled:
+    case ErrorCode::kOmahaUpdateIgnoredOverCellular:
     case ErrorCode::kPayloadTimestampError:
     case ErrorCode::kUpdatedButNotActive:
     case ErrorCode::kNoUpdate:
+    case ErrorCode::kRollbackNotPossible:
+    case ErrorCode::kFirstActiveOmahaPingSentPersistenceError:
       break;
 
     // Special flags. These can't happen (we mask them out above) but
diff --git a/mock_connection_manager.h b/mock_connection_manager.h
index e37460b..2fff68c 100644
--- a/mock_connection_manager.h
+++ b/mock_connection_manager.h
@@ -36,6 +36,7 @@
 
   MOCK_CONST_METHOD2(IsUpdateAllowedOver,
                      bool(ConnectionType type, ConnectionTethering tethering));
+  MOCK_CONST_METHOD0(IsAllowedConnectionTypesForUpdateSet, bool());
 };
 
 }  // namespace chromeos_update_engine
diff --git a/mock_metrics_reporter.h b/mock_metrics_reporter.h
index 3d39049..c678a80 100644
--- a/mock_metrics_reporter.h
+++ b/mock_metrics_reporter.h
@@ -17,6 +17,8 @@
 #ifndef UPDATE_ENGINE_MOCK_METRICS_REPORTER_H_
 #define UPDATE_ENGINE_MOCK_METRICS_REPORTER_H_
 
+#include <string>
+
 #include <gmock/gmock.h>
 
 #include "update_engine/metrics_reporter_interface.h"
@@ -29,6 +31,9 @@
 
   MOCK_METHOD1(ReportRollbackMetrics, void(metrics::RollbackResult result));
 
+  MOCK_METHOD2(ReportEnterpriseRollbackMetrics,
+               void(bool success, const std::string& rollback_version));
+
   MOCK_METHOD1(ReportDailyMetrics, void(base::TimeDelta os_age));
 
   MOCK_METHOD4(ReportUpdateCheckMetrics,
@@ -77,6 +82,13 @@
   MOCK_METHOD1(ReportTimeToReboot, void(int time_to_reboot_minutes));
 
   MOCK_METHOD2(ReportInstallDateProvisioningSource, void(int source, int max));
+
+  MOCK_METHOD1(ReportInternalErrorCode, void(ErrorCode error_code));
+
+  MOCK_METHOD3(ReportKeyVersionMetrics,
+               void(int kernel_min_version,
+                    int kernel_max_rollforward_version,
+                    bool kernel_max_rollforward_success));
 };
 
 }  // namespace chromeos_update_engine
diff --git a/mock_omaha_request_params.h b/mock_omaha_request_params.h
index 6d8d3d8..2fe5e01 100644
--- a/mock_omaha_request_params.h
+++ b/mock_omaha_request_params.h
@@ -50,6 +50,7 @@
   MOCK_METHOD3(SetTargetChannel, bool(const std::string& channel,
                                       bool is_powerwash_allowed,
                                       std::string* error));
+  MOCK_CONST_METHOD0(target_version_prefix, std::string(void));
   MOCK_METHOD0(UpdateDownloadChannel, void(void));
   MOCK_CONST_METHOD0(IsUpdateUrlOfficial, bool(void));
   MOCK_CONST_METHOD0(ShouldPowerwash, bool(void));
diff --git a/mock_payload_state.h b/mock_payload_state.h
index 259dcaf..4ac3ccf 100644
--- a/mock_payload_state.h
+++ b/mock_payload_state.h
@@ -53,6 +53,7 @@
   MOCK_METHOD1(SetScatteringWaitPeriod, void(base::TimeDelta));
   MOCK_METHOD1(SetP2PUrl, void(const std::string&));
   MOCK_METHOD0(NextPayload, bool());
+  MOCK_METHOD1(SetStagingWaitPeriod, void(base::TimeDelta));
 
   // Getters.
   MOCK_METHOD0(GetResponseSignature, std::string());
@@ -68,6 +69,8 @@
   MOCK_METHOD1(GetCurrentBytesDownloaded, uint64_t(DownloadSource source));
   MOCK_METHOD1(GetTotalBytesDownloaded, uint64_t(DownloadSource source));
   MOCK_METHOD0(GetNumReboots, uint32_t());
+  MOCK_METHOD0(GetRollbackHappened, bool());
+  MOCK_METHOD1(SetRollbackHappened, void(bool));
   MOCK_METHOD0(GetRollbackVersion, std::string());
   MOCK_METHOD0(GetP2PNumAttempts, int());
   MOCK_METHOD0(GetP2PFirstAttemptTimestamp, base::Time());
@@ -75,6 +78,7 @@
   MOCK_CONST_METHOD0(GetUsingP2PForSharing, bool());
   MOCK_METHOD0(GetScatteringWaitPeriod, base::TimeDelta());
   MOCK_CONST_METHOD0(GetP2PUrl, std::string());
+  MOCK_METHOD0(GetStagingWaitPeriod, base::TimeDelta());
 };
 
 }  // namespace chromeos_update_engine
diff --git a/mock_update_attempter.h b/mock_update_attempter.h
index d88b840..405fdd6 100644
--- a/mock_update_attempter.h
+++ b/mock_update_attempter.h
@@ -29,12 +29,14 @@
  public:
   using UpdateAttempter::UpdateAttempter;
 
-  MOCK_METHOD6(Update, void(const std::string& app_version,
-                            const std::string& omaha_url,
-                            const std::string& target_channel,
-                            const std::string& target_version_prefix,
-                            bool obey_proxies,
-                            bool interactive));
+  MOCK_METHOD7(Update,
+               void(const std::string& app_version,
+                    const std::string& omaha_url,
+                    const std::string& target_channel,
+                    const std::string& target_version_prefix,
+                    bool rollback_allowed,
+                    bool obey_proxies,
+                    bool interactive));
 
   MOCK_METHOD1(GetStatus, bool(update_engine::UpdateEngineStatus* out_status));
 
diff --git a/omaha_request_action.cc b/omaha_request_action.cc
index 78d8d50..95dee51 100644
--- a/omaha_request_action.cc
+++ b/omaha_request_action.cc
@@ -18,6 +18,7 @@
 
 #include <inttypes.h>
 
+#include <limits>
 #include <map>
 #include <sstream>
 #include <string>
@@ -35,6 +36,7 @@
 #include <brillo/key_value_store.h>
 #include <expat.h>
 #include <metrics/metrics_library.h>
+#include <policy/libpolicy.h>
 
 #include "update_engine/common/action_pipe.h"
 #include "update_engine/common/constants.h"
@@ -52,7 +54,9 @@
 
 using base::Time;
 using base::TimeDelta;
+using chromeos_update_manager::kRollforwardInfinity;
 using std::map;
+using std::numeric_limits;
 using std::string;
 using std::vector;
 
@@ -87,6 +91,9 @@
 
 // updatecheck attributes (without the underscore prefix).
 static const char* kEolAttr = "eol";
+static const char* kRollback = "rollback";
+static const char* kFirmwareVersion = "firmware_version";
+static const char* kKernelVersion = "kernel_version";
 
 namespace {
 
@@ -126,10 +133,17 @@
     if (include_ping)
         app_body = GetPingXml(ping_active_days, ping_roll_call_days);
     if (!ping_only) {
-      app_body += base::StringPrintf(
-          "        <updatecheck targetversionprefix=\"%s\""
-          "></updatecheck>\n",
-          XmlEncodeWithDefault(params->target_version_prefix(), "").c_str());
+      app_body += "        <updatecheck";
+      if (!params->target_version_prefix().empty()) {
+        app_body += base::StringPrintf(
+            " targetversionprefix=\"%s\"",
+            XmlEncodeWithDefault(params->target_version_prefix(), "").c_str());
+        // Rollback requires target_version_prefix set.
+        if (params->rollback_allowed()) {
+          app_body += " rollback_allowed=\"true\"";
+        }
+      }
+      app_body += "></updatecheck>\n";
 
       // If this is the first update check after a reboot following a previous
       // update, generate an event containing the previous version number. If
@@ -620,12 +634,14 @@
     std::unique_ptr<HttpFetcher> http_fetcher,
     bool ping_only)
     : system_state_(system_state),
+      params_(system_state->request_params()),
       event_(event),
       http_fetcher_(std::move(http_fetcher)),
+      policy_provider_(std::make_unique<policy::PolicyProvider>()),
       ping_only_(ping_only),
       ping_active_days_(0),
       ping_roll_call_days_(0) {
-  params_ = system_state->request_params();
+  policy_provider_->Reload();
 }
 
 OmahaRequestAction::~OmahaRequestAction() {}
@@ -785,11 +801,12 @@
 
 // We just store the response in the buffer. Once we've received all bytes,
 // we'll look in the buffer and decide what to do.
-void OmahaRequestAction::ReceivedBytes(HttpFetcher *fetcher,
+bool OmahaRequestAction::ReceivedBytes(HttpFetcher* fetcher,
                                        const void* bytes,
                                        size_t length) {
   const uint8_t* byte_ptr = reinterpret_cast<const uint8_t*>(bytes);
   response_buffer_.insert(response_buffer_.end(), byte_ptr, byte_ptr + length);
+  return true;
 }
 
 namespace {
@@ -924,6 +941,20 @@
   return true;
 }
 
+// Parses the 2 key version strings kernel_version and firmware_version. If the
+// field is not present, or cannot be parsed the values default to 0xffff.
+void ParseRollbackVersions(OmahaParserData* parser_data,
+                           OmahaResponse* output_object) {
+  utils::ParseRollbackKeyVersion(
+      parser_data->updatecheck_attrs[kFirmwareVersion],
+      &output_object->rollback_key_version.firmware_key,
+      &output_object->rollback_key_version.firmware);
+  utils::ParseRollbackKeyVersion(
+      parser_data->updatecheck_attrs[kKernelVersion],
+      &output_object->rollback_key_version.kernel_key,
+      &output_object->rollback_key_version.kernel);
+}
+
 }  // namespace
 
 bool OmahaRequestAction::ParseResponse(OmahaParserData* parser_data,
@@ -986,6 +1017,14 @@
 
   // Parse the updatecheck attributes.
   PersistEolStatus(parser_data->updatecheck_attrs);
+  // Rollback-related updatecheck attributes.
+  // Defaults to false if attribute is not present.
+  output_object->is_rollback =
+      ParseBool(parser_data->updatecheck_attrs[kRollback]);
+
+  // Parses the rollback versions of the current image. If the fields do not
+  // exist they default to 0xffff for the 4 key versions.
+  ParseRollbackVersions(parser_data, output_object);
 
   if (!ParseStatus(parser_data, output_object, completer))
     return false;
@@ -1102,6 +1141,9 @@
 
   PayloadStateInterface* const payload_state = system_state_->payload_state();
 
+  // Set the max kernel key version based on whether rollback is allowed.
+  SetMaxKernelKeyVersionForRollback();
+
   // Events are best effort transactions -- assume they always succeed.
   if (IsEvent()) {
     CHECK(!HasOutputPipe()) << "No output pipe allowed for event requests.";
@@ -1131,13 +1173,13 @@
       reinterpret_cast<const char*>(response_buffer_.data()),
       response_buffer_.size(),
       XML_TRUE);
-  XML_ParserFree(parser);
 
   if (res != XML_STATUS_OK || parser_data.failed) {
     LOG(ERROR) << "Omaha response not valid XML: "
                << XML_ErrorString(XML_GetErrorCode(parser))
                << " at line " << XML_GetCurrentLineNumber(parser)
                << " col " << XML_GetCurrentColumnNumber(parser);
+    XML_ParserFree(parser);
     ErrorCode error_code = ErrorCode::kOmahaRequestXMLParseError;
     if (response_buffer_.empty()) {
       error_code = ErrorCode::kOmahaRequestEmptyResponseError;
@@ -1147,6 +1189,7 @@
     completer.set_code(error_code);
     return;
   }
+  XML_ParserFree(parser);
 
   // Update the last ping day preferences based on the server daystart response
   // even if we didn't send a ping. Omaha always includes the daystart in the
@@ -1161,7 +1204,10 @@
   // their a=-1 in the past and we have to set first_active_omaha_ping_sent for
   // future checks.
   if (!system_state_->hardware()->GetFirstActiveOmahaPingSent()) {
-    system_state_->hardware()->SetFirstActiveOmahaPingSent();
+    if (!system_state_->hardware()->SetFirstActiveOmahaPingSent()) {
+      system_state_->metrics_reporter()->ReportInternalErrorCode(
+          ErrorCode::kFirstActiveOmahaPingSentPersistenceError);
+    }
   }
 
   if (!HasOutputPipe()) {
@@ -1177,9 +1223,11 @@
   output_object.update_exists = true;
   SetOutputObject(output_object);
 
-  if (ShouldIgnoreUpdate(output_object)) {
-    output_object.update_exists = false;
-    completer.set_code(ErrorCode::kOmahaUpdateIgnoredPerPolicy);
+  ErrorCode error = ErrorCode::kSuccess;
+  if (ShouldIgnoreUpdate(&error, output_object)) {
+    // No need to change output_object.update_exists here, since the value
+    // has been output to the pipe.
+    completer.set_code(error);
     return;
   }
 
@@ -1237,7 +1285,8 @@
 
   if (system_state_->hardware()->IsOOBEEnabled() &&
       !system_state_->hardware()->IsOOBEComplete(nullptr) &&
-      output_object.deadline.empty() &&
+      (output_object.deadline.empty() ||
+       payload_state->GetRollbackHappened()) &&
       params_->app_version() != "ForcedUpdate") {
     output_object.update_exists = false;
     LOG(INFO) << "Ignoring non-critical Omaha updates until OOBE is done.";
@@ -1433,13 +1482,18 @@
       system_state_->clock()->GetWallclockTime() - update_first_seen_at;
   TimeDelta max_scatter_period =
       TimeDelta::FromDays(output_object->max_days_to_scatter);
+  int64_t staging_wait_time_in_days = 0;
+  // Use staging and its default max value if staging is on.
+  if (system_state_->prefs()->GetInt64(kPrefsWallClockStagingWaitPeriod,
+                                       &staging_wait_time_in_days) &&
+      staging_wait_time_in_days > 0)
+    max_scatter_period = TimeDelta::FromDays(kMaxWaitTimeStagingInDays);
 
   LOG(INFO) << "Waiting Period = "
             << utils::FormatSecs(params_->waiting_period().InSeconds())
             << ", Time Elapsed = "
             << utils::FormatSecs(elapsed_time.InSeconds())
-            << ", MaxDaysToScatter = "
-            << max_scatter_period.InDays();
+            << ", MaxDaysToScatter = " << max_scatter_period.InDays();
 
   if (!output_object->deadline.empty()) {
     // The deadline is set for all rules which serve a delta update from a
@@ -1639,6 +1693,7 @@
     break;
 
   case ErrorCode::kOmahaUpdateIgnoredPerPolicy:
+  case ErrorCode::kOmahaUpdateIgnoredOverCellular:
     result = metrics::CheckResult::kUpdateAvailable;
     reaction = metrics::CheckReaction::kIgnored;
     break;
@@ -1673,7 +1728,7 @@
 }
 
 bool OmahaRequestAction::ShouldIgnoreUpdate(
-    const OmahaResponse& response) const {
+    ErrorCode* error, const OmahaResponse& response) const {
   // Note: policy decision to not update to a version we rolled back from.
   string rollback_version =
       system_state_->payload_state()->GetRollbackVersion();
@@ -1681,11 +1736,12 @@
     LOG(INFO) << "Detected previous rollback from version " << rollback_version;
     if (rollback_version == response.version) {
       LOG(INFO) << "Received version that we rolled back from. Ignoring.";
+      *error = ErrorCode::kOmahaUpdateIgnoredPerPolicy;
       return true;
     }
   }
 
-  if (!IsUpdateAllowedOverCurrentConnection()) {
+  if (!IsUpdateAllowedOverCurrentConnection(error, response)) {
     LOG(INFO) << "Update is not allowed over current connection.";
     return true;
   }
@@ -1700,7 +1756,62 @@
   return false;
 }
 
-bool OmahaRequestAction::IsUpdateAllowedOverCurrentConnection() const {
+bool OmahaRequestAction::IsUpdateAllowedOverCellularByPrefs(
+    const OmahaResponse& response) const {
+  PrefsInterface* prefs = system_state_->prefs();
+
+  if (!prefs) {
+    LOG(INFO) << "Disabling updates over cellular as the preferences are "
+                 "not available.";
+    return false;
+  }
+
+  bool is_allowed;
+
+  if (prefs->Exists(kPrefsUpdateOverCellularPermission) &&
+      prefs->GetBoolean(kPrefsUpdateOverCellularPermission, &is_allowed) &&
+      is_allowed) {
+    LOG(INFO) << "Allowing updates over cellular as permission preference is "
+                 "set to true.";
+    return true;
+  }
+
+  if (!prefs->Exists(kPrefsUpdateOverCellularTargetVersion) ||
+      !prefs->Exists(kPrefsUpdateOverCellularTargetSize)) {
+    LOG(INFO) << "Disabling updates over cellular as permission preference is "
+                 "set to false or does not exist while target does not exist.";
+    return false;
+  }
+
+  std::string target_version;
+  int64_t target_size;
+
+  if (!prefs->GetString(kPrefsUpdateOverCellularTargetVersion,
+                        &target_version) ||
+      !prefs->GetInt64(kPrefsUpdateOverCellularTargetSize, &target_size)) {
+    LOG(INFO) << "Disabling updates over cellular as the target version or "
+                 "size is not accessible.";
+    return false;
+  }
+
+  uint64_t total_packages_size = 0;
+  for (const auto& package : response.packages) {
+    total_packages_size += package.size;
+  }
+  if (target_version == response.version &&
+      static_cast<uint64_t>(target_size) == total_packages_size) {
+    LOG(INFO) << "Allowing updates over cellular as the target matches the"
+                 "omaha response.";
+    return true;
+  } else {
+    LOG(INFO) << "Disabling updates over cellular as the target does not"
+                 "match the omaha response.";
+    return false;
+  }
+}
+
+bool OmahaRequestAction::IsUpdateAllowedOverCurrentConnection(
+    ErrorCode* error, const OmahaResponse& response) const {
   ConnectionType type;
   ConnectionTethering tethering;
   ConnectionManagerInterface* connection_manager =
@@ -1710,11 +1821,97 @@
               << "Defaulting to allow updates.";
     return true;
   }
+
   bool is_allowed = connection_manager->IsUpdateAllowedOver(type, tethering);
+  bool is_device_policy_set =
+      connection_manager->IsAllowedConnectionTypesForUpdateSet();
+  // Treats tethered connection as if it is cellular connection.
+  bool is_over_cellular = type == ConnectionType::kCellular ||
+                          tethering == ConnectionTethering::kConfirmed;
+
+  if (!is_over_cellular) {
+    // There's no need to further check user preferences as we are not over
+    // cellular connection.
+    if (!is_allowed)
+      *error = ErrorCode::kOmahaUpdateIgnoredPerPolicy;
+  } else if (is_device_policy_set) {
+    // There's no need to further check user preferences as the device policy
+    // is set regarding updates over cellular.
+    if (!is_allowed)
+      *error = ErrorCode::kOmahaUpdateIgnoredPerPolicy;
+  } else {
+    // Deivce policy is not set, so user preferences overwrite whether to
+    // allow updates over cellular.
+    is_allowed = IsUpdateAllowedOverCellularByPrefs(response);
+    if (!is_allowed)
+      *error = ErrorCode::kOmahaUpdateIgnoredOverCellular;
+  }
+
   LOG(INFO) << "We are connected via "
             << connection_utils::StringForConnectionType(type)
             << ", Updates allowed: " << (is_allowed ? "Yes" : "No");
   return is_allowed;
 }
 
+bool OmahaRequestAction::IsRollbackEnabled() const {
+  if (policy_provider_->IsConsumerDevice()) {
+    LOG(INFO) << "Rollback is not enabled for consumer devices.";
+    return false;
+  }
+
+  if (!policy_provider_->device_policy_is_loaded()) {
+    LOG(INFO) << "No device policy is loaded. Assuming rollback enabled.";
+    return true;
+  }
+
+  int allowed_milestones;
+  if (!policy_provider_->GetDevicePolicy().GetRollbackAllowedMilestones(
+          &allowed_milestones)) {
+    LOG(INFO) << "RollbackAllowedMilestones policy can't be read. "
+                 "Defaulting to rollback enabled.";
+    return true;
+  }
+
+  LOG(INFO) << "Rollback allows " << allowed_milestones << " milestones.";
+  return allowed_milestones > 0;
+}
+
+void OmahaRequestAction::SetMaxKernelKeyVersionForRollback() const {
+  int max_kernel_rollforward;
+  int min_kernel_version = system_state_->hardware()->GetMinKernelKeyVersion();
+  if (IsRollbackEnabled()) {
+    // If rollback is enabled, set the max kernel key version to the current
+    // kernel key version. This has the effect of freezing kernel key roll
+    // forwards.
+    //
+    // TODO(zentaro): This behavior is temporary, and ensures that no kernel
+    // key roll forward happens until the server side components of rollback
+    // are implemented. Future changes will allow the Omaha server to return
+    // the kernel key version from max_rollback_versions in the past. At that
+    // point the max kernel key version will be set to that value, creating a
+    // sliding window of versions that can be rolled back to.
+    LOG(INFO) << "Rollback is enabled. Setting kernel_max_rollforward to "
+              << min_kernel_version;
+    max_kernel_rollforward = min_kernel_version;
+  } else {
+    // For devices that are not rollback enabled (ie. consumer devices), the
+    // max kernel key version is set to 0xfffffffe, which is logically
+    // infinity. This maintains the previous behavior that that kernel key
+    // versions roll forward each time they are incremented.
+    LOG(INFO) << "Rollback is disabled. Setting kernel_max_rollforward to "
+              << kRollforwardInfinity;
+    max_kernel_rollforward = kRollforwardInfinity;
+  }
+
+  bool max_rollforward_set =
+      system_state_->hardware()->SetMaxKernelKeyRollforward(
+          max_kernel_rollforward);
+  if (!max_rollforward_set) {
+    LOG(ERROR) << "Failed to set kernel_max_rollforward";
+  }
+  // Report metrics
+  system_state_->metrics_reporter()->ReportKeyVersionMetrics(
+      min_kernel_version, max_kernel_rollforward, max_rollforward_set);
+}
+
 }  // namespace chromeos_update_engine
diff --git a/omaha_request_action.h b/omaha_request_action.h
index 924da40..1034c3f 100644
--- a/omaha_request_action.h
+++ b/omaha_request_action.h
@@ -39,6 +39,10 @@
 // The Omaha Request action makes a request to Omaha and can output
 // the response on the output ActionPipe.
 
+namespace policy {
+class PolicyProvider;
+}
+
 namespace chromeos_update_engine {
 
 // Encodes XML entities in a given string. Input must be ASCII-7 valid. If
@@ -123,6 +127,10 @@
   // fallback ones.
   static const int kDefaultMaxFailureCountPerUrl = 10;
 
+  // If staging is enabled, set the maximum wait time to 28 days, since that is
+  // the predetermined wait time for staging.
+  static const int kMaxWaitTimeStagingInDays = 28;
+
   // These are the possible outcome upon checking whether we satisfied
   // the wall-clock-based-wait.
   enum WallClockWaitResult {
@@ -163,8 +171,9 @@
   std::string Type() const override { return StaticType(); }
 
   // Delegate methods (see http_fetcher.h)
-  void ReceivedBytes(HttpFetcher *fetcher,
-                     const void* bytes, size_t length) override;
+  bool ReceivedBytes(HttpFetcher* fetcher,
+                     const void* bytes,
+                     size_t length) override;
 
   void TransferComplete(HttpFetcher *fetcher, bool successful) override;
 
@@ -172,6 +181,8 @@
   bool IsEvent() const { return event_.get() != nullptr; }
 
  private:
+  friend class OmahaRequestActionTest;
+  friend class OmahaRequestActionTestProcessorDelegate;
   FRIEND_TEST(OmahaRequestActionTest, GetInstallDateWhenNoPrefsNorOOBE);
   FRIEND_TEST(OmahaRequestActionTest,
               GetInstallDateWhenOOBECompletedWithInvalidDate);
@@ -292,11 +303,24 @@
   void OnLookupPayloadViaP2PCompleted(const std::string& url);
 
   // Returns true if the current update should be ignored.
-  bool ShouldIgnoreUpdate(const OmahaResponse& response) const;
+  bool ShouldIgnoreUpdate(ErrorCode* error,
+                          const OmahaResponse& response) const;
+
+  // Return true if updates are allowed by user preferences.
+  bool IsUpdateAllowedOverCellularByPrefs(const OmahaResponse& response) const;
 
   // Returns true if updates are allowed over the current type of connection.
   // False otherwise.
-  bool IsUpdateAllowedOverCurrentConnection() const;
+  bool IsUpdateAllowedOverCurrentConnection(
+      ErrorCode* error, const OmahaResponse& response) const;
+
+  // Returns true if rollback is enabled. Always returns false for consumer
+  // devices.
+  bool IsRollbackEnabled() const;
+
+  // Sets the appropriate max kernel key version based on whether rollback is
+  // enabled.
+  void SetMaxKernelKeyVersionForRollback() const;
 
   // Global system context.
   SystemState* system_state_;
@@ -310,6 +334,9 @@
   // pointer to the HttpFetcher that does the http work
   std::unique_ptr<HttpFetcher> http_fetcher_;
 
+  // Used for fetching information about the device policy.
+  std::unique_ptr<policy::PolicyProvider> policy_provider_;
+
   // If true, only include the <ping> element in the request.
   bool ping_only_;
 
diff --git a/omaha_request_action_unittest.cc b/omaha_request_action_unittest.cc
index 9a01a13..1e0ad6d 100644
--- a/omaha_request_action_unittest.cc
+++ b/omaha_request_action_unittest.cc
@@ -20,6 +20,7 @@
 
 #include <memory>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include <base/bind.h>
@@ -35,6 +36,8 @@
 #include <brillo/message_loops/message_loop.h>
 #include <brillo/message_loops/message_loop_utils.h>
 #include <gtest/gtest.h>
+#include <policy/libpolicy.h>
+#include <policy/mock_libpolicy.h>
 
 #include "update_engine/common/action_pipe.h"
 #include "update_engine/common/constants.h"
@@ -49,9 +52,11 @@
 #include "update_engine/mock_connection_manager.h"
 #include "update_engine/mock_payload_state.h"
 #include "update_engine/omaha_request_params.h"
+#include "update_engine/update_manager/rollback_prefs.h"
 
 using base::Time;
 using base::TimeDelta;
+using chromeos_update_manager::kRollforwardInfinity;
 using std::string;
 using std::vector;
 using testing::AllOf;
@@ -61,6 +66,7 @@
 using testing::Le;
 using testing::NiceMock;
 using testing::Return;
+using testing::ReturnRef;
 using testing::ReturnPointee;
 using testing::SaveArg;
 using testing::SetArgPointee;
@@ -68,12 +74,26 @@
 
 namespace {
 
+static_assert(kRollforwardInfinity == 0xfffffffe,
+              "Don't change the value of kRollforward infinity unless its "
+              "size has been changed in firmware.");
+
 const char kTestAppId[] = "test-app-id";
 const char kTestAppId2[] = "test-app2-id";
 
 // This is a helper struct to allow unit tests build an update response with the
 // values they care about.
 struct FakeUpdateResponse {
+  string GetRollbackVersionAttributes() const {
+    return (rollback ? " _rollback=\"true\"" : "") +
+           (!rollback_firmware_version.empty()
+                ? " _firmware_version=\"" + rollback_firmware_version + "\""
+                : "") +
+           (!rollback_kernel_version.empty()
+                ? " _kernel_version=\"" + rollback_kernel_version + "\""
+                : "");
+  }
+
   string GetNoUpdateResponse() const {
     string entity_str;
     if (include_entity)
@@ -111,8 +131,8 @@
                       "\" cohortname=\"" + cohortname + "\" "
                 : "") +
            " status=\"ok\">"
-           "<ping status=\"ok\"/><updatecheck status=\"ok\">"
-           "<urls><url codebase=\"" +
+           "<ping status=\"ok\"/><updatecheck status=\"ok\"" +
+           GetRollbackVersionAttributes() + ">" + "<urls><url codebase=\"" +
            codebase +
            "\"/></urls>"
            "<manifest version=\"" +
@@ -210,15 +230,88 @@
   bool multi_app_no_update = false;
   // Whether to include more than one package in an app.
   bool multi_package = false;
+
+  // Whether the payload is a rollback.
+  bool rollback = false;
+  // The verified boot firmware key version for the rollback image.
+  string rollback_firmware_version = "";
+  // The verified boot kernel key version for the rollback image.
+  string rollback_kernel_version = "";
 };
 
 }  // namespace
 
 namespace chromeos_update_engine {
 
+class OmahaRequestActionTestProcessorDelegate : public ActionProcessorDelegate {
+ public:
+  OmahaRequestActionTestProcessorDelegate()
+      : expected_code_(ErrorCode::kSuccess),
+        interactive_(false),
+        test_http_fetcher_headers_(false) {}
+  ~OmahaRequestActionTestProcessorDelegate() override = default;
+
+  void ProcessingDone(const ActionProcessor* processor,
+                      ErrorCode code) override {
+    brillo::MessageLoop::current()->BreakLoop();
+  }
+
+  void ActionCompleted(ActionProcessor* processor,
+                       AbstractAction* action,
+                       ErrorCode code) override {
+    // Make sure actions always succeed.
+    if (action->Type() == OmahaRequestAction::StaticType()) {
+      EXPECT_EQ(expected_code_, code);
+      // Check that the headers were set in the fetcher during the action. Note
+      // that we set this request as "interactive".
+      auto fetcher = static_cast<const MockHttpFetcher*>(
+          static_cast<OmahaRequestAction*>(action)->http_fetcher_.get());
+
+      if (test_http_fetcher_headers_) {
+        EXPECT_EQ(interactive_ ? "fg" : "bg",
+                  fetcher->GetHeader("X-Goog-Update-Interactivity"));
+        EXPECT_EQ(kTestAppId, fetcher->GetHeader("X-Goog-Update-AppId"));
+        EXPECT_NE("", fetcher->GetHeader("X-Goog-Update-Updater"));
+      }
+      post_data_ = fetcher->post_data();
+    } else if (action->Type() ==
+               ObjectCollectorAction<OmahaResponse>::StaticType()) {
+      EXPECT_EQ(ErrorCode::kSuccess, code);
+      auto collector_action =
+          static_cast<ObjectCollectorAction<OmahaResponse>*>(action);
+      omaha_response_.reset(new OmahaResponse(collector_action->object()));
+      EXPECT_TRUE(omaha_response_);
+    } else {
+      EXPECT_EQ(ErrorCode::kSuccess, code);
+    }
+  }
+  ErrorCode expected_code_;
+  brillo::Blob post_data_;
+  bool interactive_;
+  bool test_http_fetcher_headers_;
+  std::unique_ptr<OmahaResponse> omaha_response_;
+};
+
 class OmahaRequestActionTest : public ::testing::Test {
  protected:
   void SetUp() override {
+    request_params_.set_os_sp("service_pack");
+    request_params_.set_os_board("x86-generic");
+    request_params_.set_app_id(kTestAppId);
+    request_params_.set_app_version("0.1.0.0");
+    request_params_.set_app_lang("en-US");
+    request_params_.set_current_channel("unittest");
+    request_params_.set_target_channel("unittest");
+    request_params_.set_hwid("OEM MODEL 09235 7471");
+    request_params_.set_fw_version("ChromeOSFirmware.1.0");
+    request_params_.set_ec_version("0X0A1");
+    request_params_.set_delta_okay(true);
+    request_params_.set_interactive(false);
+    request_params_.set_update_url("http://url");
+    request_params_.set_target_version_prefix("");
+    request_params_.set_rollback_allowed(false);
+    request_params_.set_is_powerwash_allowed(false);
+
     fake_system_state_.set_request_params(&request_params_);
     fake_system_state_.set_prefs(&fake_prefs_);
   }
@@ -238,8 +331,22 @@
   // about reporting UpdateEngine.Check.{Result,Reaction,DownloadError}
   // UMA statistics. Use the appropriate ::kUnset value to specify that
   // the given metric should not be reported.
-  bool TestUpdateCheck(OmahaRequestParams* request_params,
-                       const string& http_response,
+  bool TestUpdateCheck(const string& http_response,
+                       int fail_http_response_code,
+                       bool ping_only,
+                       bool is_consumer_device,
+                       int rollback_allowed_milestones,
+                       bool is_policy_loaded,
+                       ErrorCode expected_code,
+                       metrics::CheckResult expected_check_result,
+                       metrics::CheckReaction expected_check_reaction,
+                       metrics::DownloadErrorCode expected_download_error_code,
+                       OmahaResponse* out_response,
+                       brillo::Blob* out_post_data);
+
+  // Overload of TestUpdateCheck that does not supply |is_consumer_device| or
+  // |rollback_allowed_milestones| which are only required for rollback tests.
+  bool TestUpdateCheck(const string& http_response,
                        int fail_http_response_code,
                        bool ping_only,
                        ErrorCode expected_code,
@@ -249,6 +356,15 @@
                        OmahaResponse* out_response,
                        brillo::Blob* out_post_data);
 
+  void TestRollbackCheck(bool is_consumer_device,
+                         int rollback_allowed_milestones,
+                         bool is_policy_loaded,
+                         OmahaResponse* out_response);
+
+  void TestEvent(OmahaEvent* event,
+                 const string& http_response,
+                 brillo::Blob* out_post_data);
+
   // Runs and checks a ping test. |ping_only| indicates whether it should send
   // only a ping or also an updatecheck.
   void PingTest(bool ping_only);
@@ -272,98 +388,23 @@
 
   FakeSystemState fake_system_state_;
   FakeUpdateResponse fake_update_response_;
-
-  // By default, all tests use these objects unless they replace them in the
-  // fake_system_state_.
-  OmahaRequestParams request_params_ = OmahaRequestParams{
-      &fake_system_state_,
-      constants::kOmahaPlatformName,
-      OmahaRequestParams::kOsVersion,
-      "service_pack",
-      "x86-generic",
-      kTestAppId,
-      "0.1.0.0",
-      "en-US",
-      "unittest",
-      "OEM MODEL 09235 7471",
-      "ChromeOSFirmware.1.0",
-      "0X0A1",
-      false,   // delta okay
-      false,   // interactive
-      "http://url",
-      ""};     // target_version_prefix
+  // Used by all tests.
+  OmahaRequestParams request_params_{&fake_system_state_};
 
   FakePrefs fake_prefs_;
-};
 
-namespace {
-class OmahaRequestActionTestProcessorDelegate : public ActionProcessorDelegate {
- public:
-  OmahaRequestActionTestProcessorDelegate()
-      : expected_code_(ErrorCode::kSuccess) {}
-  ~OmahaRequestActionTestProcessorDelegate() override {
-  }
-  void ProcessingDone(const ActionProcessor* processor,
-                      ErrorCode code) override {
-    brillo::MessageLoop::current()->BreakLoop();
-  }
+  OmahaRequestActionTestProcessorDelegate delegate_;
 
-  void ActionCompleted(ActionProcessor* processor,
-                       AbstractAction* action,
-                       ErrorCode code) override {
-    // make sure actions always succeed
-    if (action->Type() == OmahaRequestAction::StaticType())
-      EXPECT_EQ(expected_code_, code);
-    else
-      EXPECT_EQ(ErrorCode::kSuccess, code);
-  }
-  ErrorCode expected_code_;
-};
-}  // namespace
-
-class OutputObjectCollectorAction;
-
-template<>
-class ActionTraits<OutputObjectCollectorAction> {
- public:
-  // Does not take an object for input
-  typedef OmahaResponse InputObjectType;
-  // On success, puts the output path on output
-  typedef NoneType OutputObjectType;
-};
-
-class OutputObjectCollectorAction : public Action<OutputObjectCollectorAction> {
- public:
-  OutputObjectCollectorAction() : has_input_object_(false) {}
-  void PerformAction() {
-    // copy input object
-    has_input_object_ = HasInputObject();
-    if (has_input_object_)
-      omaha_response_ = GetInputObject();
-    processor_->ActionComplete(this, ErrorCode::kSuccess);
-  }
-  // Should never be called
-  void TerminateProcessing() {
-    CHECK(false);
-  }
-  // Debugging/logging
-  static string StaticType() {
-    return "OutputObjectCollectorAction";
-  }
-  string Type() const { return StaticType(); }
-  using InputObjectType =
-      ActionTraits<OutputObjectCollectorAction>::InputObjectType;
-  using OutputObjectType =
-      ActionTraits<OutputObjectCollectorAction>::OutputObjectType;
-  bool has_input_object_;
-  OmahaResponse omaha_response_;
+  bool test_http_fetcher_headers_{false};
 };
 
 bool OmahaRequestActionTest::TestUpdateCheck(
-    OmahaRequestParams* request_params,
     const string& http_response,
     int fail_http_response_code,
     bool ping_only,
+    bool is_consumer_device,
+    int rollback_allowed_milestones,
+    bool is_policy_loaded,
     ErrorCode expected_code,
     metrics::CheckResult expected_check_result,
     metrics::CheckReaction expected_check_reaction,
@@ -372,28 +413,47 @@
     brillo::Blob* out_post_data) {
   brillo::FakeMessageLoop loop(nullptr);
   loop.SetAsCurrent();
-  MockHttpFetcher* fetcher = new MockHttpFetcher(http_response.data(),
-                                                 http_response.size(),
-                                                 nullptr);
+  auto fetcher = std::make_unique<MockHttpFetcher>(
+      http_response.data(), http_response.size(), nullptr);
   if (fail_http_response_code >= 0) {
     fetcher->FailTransfer(fail_http_response_code);
   }
-  if (request_params)
-    fake_system_state_.set_request_params(request_params);
-  OmahaRequestAction action(&fake_system_state_,
-                            nullptr,
-                            base::WrapUnique(fetcher),
-                            ping_only);
-  OmahaRequestActionTestProcessorDelegate delegate;
-  delegate.expected_code_ = expected_code;
+  // This ensures the tests didn't forget to update fake_system_state_ if they
+  // are not using the default request_params_.
+  EXPECT_EQ(&request_params_, fake_system_state_.request_params());
 
+  auto omaha_request_action = std::make_unique<OmahaRequestAction>(
+      &fake_system_state_, nullptr, std::move(fetcher), ping_only);
+
+  auto mock_policy_provider =
+      std::make_unique<NiceMock<policy::MockPolicyProvider>>();
+  EXPECT_CALL(*mock_policy_provider, IsConsumerDevice())
+      .WillRepeatedly(Return(is_consumer_device));
+
+  EXPECT_CALL(*mock_policy_provider, device_policy_is_loaded())
+      .WillRepeatedly(Return(is_policy_loaded));
+
+  const policy::MockDevicePolicy device_policy;
+  const bool get_allowed_milestone_succeeds = rollback_allowed_milestones >= 0;
+  EXPECT_CALL(device_policy, GetRollbackAllowedMilestones(_))
+      .WillRepeatedly(DoAll(SetArgPointee<0>(rollback_allowed_milestones),
+                            Return(get_allowed_milestone_succeeds)));
+
+  EXPECT_CALL(*mock_policy_provider, GetDevicePolicy())
+      .WillRepeatedly(ReturnRef(device_policy));
+  omaha_request_action->policy_provider_ = std::move(mock_policy_provider);
+
+  delegate_.expected_code_ = expected_code;
+  delegate_.interactive_ = request_params_.interactive();
+  delegate_.test_http_fetcher_headers_ = test_http_fetcher_headers_;
   ActionProcessor processor;
-  processor.set_delegate(&delegate);
-  processor.EnqueueAction(&action);
+  processor.set_delegate(&delegate_);
 
-  OutputObjectCollectorAction collector_action;
-  BondActions(&action, &collector_action);
-  processor.EnqueueAction(&collector_action);
+  auto collector_action =
+      std::make_unique<ObjectCollectorAction<OmahaResponse>>();
+  BondActions(omaha_request_action.get(), collector_action.get());
+  processor.EnqueueAction(std::move(omaha_request_action));
+  processor.EnqueueAction(std::move(collector_action));
 
   EXPECT_CALL(*fake_system_state_.mock_metrics_reporter(),
               ReportUpdateCheckMetrics(_, _, _, _))
@@ -411,35 +471,75 @@
       base::Unretained(&processor)));
   loop.Run();
   EXPECT_FALSE(loop.PendingTasks());
-  if (collector_action.has_input_object_ && out_response)
-    *out_response = collector_action.omaha_response_;
+  if (delegate_.omaha_response_ && out_response)
+    *out_response = *delegate_.omaha_response_;
   if (out_post_data)
-    *out_post_data = fetcher->post_data();
-  return collector_action.has_input_object_;
+    *out_post_data = delegate_.post_data_;
+  return delegate_.omaha_response_ != nullptr;
 }
 
-// Tests Event requests -- they should always succeed. |out_post_data|
-// may be null; if non-null, the post-data received by the mock
-// HttpFetcher is returned.
-void TestEvent(OmahaRequestParams params,
-               OmahaEvent* event,
-               const string& http_response,
-               brillo::Blob* out_post_data) {
+bool OmahaRequestActionTest::TestUpdateCheck(
+    const string& http_response,
+    int fail_http_response_code,
+    bool ping_only,
+    ErrorCode expected_code,
+    metrics::CheckResult expected_check_result,
+    metrics::CheckReaction expected_check_reaction,
+    metrics::DownloadErrorCode expected_download_error_code,
+    OmahaResponse* out_response,
+    brillo::Blob* out_post_data) {
+  return TestUpdateCheck(http_response,
+                         fail_http_response_code,
+                         ping_only,
+                         true,   // is_consumer_device
+                         0,      // rollback_allowed_milestones
+                         false,  // is_policy_loaded
+                         expected_code,
+                         expected_check_result,
+                         expected_check_reaction,
+                         expected_download_error_code,
+                         out_response,
+                         out_post_data);
+}
+
+void OmahaRequestActionTest::TestRollbackCheck(bool is_consumer_device,
+                                               int rollback_allowed_milestones,
+                                               bool is_policy_loaded,
+                                               OmahaResponse* out_response) {
+  fake_update_response_.deadline = "20101020";
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              is_consumer_device,
+                              rollback_allowed_milestones,
+                              is_policy_loaded,
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kUpdateAvailable,
+                              metrics::CheckReaction::kUpdating,
+                              metrics::DownloadErrorCode::kUnset,
+                              out_response,
+                              nullptr));
+  ASSERT_TRUE(out_response->update_exists);
+}
+
+// Tests Event requests -- they should always succeed. |out_post_data| may be
+// null; if non-null, the post-data received by the mock HttpFetcher is
+// returned.
+void OmahaRequestActionTest::TestEvent(OmahaEvent* event,
+                                       const string& http_response,
+                                       brillo::Blob* out_post_data) {
   brillo::FakeMessageLoop loop(nullptr);
   loop.SetAsCurrent();
-  MockHttpFetcher* fetcher = new MockHttpFetcher(http_response.data(),
-                                                 http_response.size(),
-                                                 nullptr);
-  FakeSystemState fake_system_state;
-  fake_system_state.set_request_params(&params);
-  OmahaRequestAction action(&fake_system_state,
-                            event,
-                            base::WrapUnique(fetcher),
-                            false);
-  OmahaRequestActionTestProcessorDelegate delegate;
+
+  auto action = std::make_unique<OmahaRequestAction>(
+      &fake_system_state_,
+      event,
+      std::make_unique<MockHttpFetcher>(
+          http_response.data(), http_response.size(), nullptr),
+      false);
   ActionProcessor processor;
-  processor.set_delegate(&delegate);
-  processor.EnqueueAction(&action);
+  processor.set_delegate(&delegate_);
+  processor.EnqueueAction(std::move(action));
 
   loop.PostTask(base::Bind(
       [](ActionProcessor* processor) { processor->StartProcessing(); },
@@ -448,47 +548,42 @@
   EXPECT_FALSE(loop.PendingTasks());
 
   if (out_post_data)
-    *out_post_data = fetcher->post_data();
+    *out_post_data = delegate_.post_data_;
 }
 
 TEST_F(OmahaRequestActionTest, RejectEntities) {
   OmahaResponse response;
   fake_update_response_.include_entity = true;
-  ASSERT_FALSE(
-      TestUpdateCheck(nullptr,  // request_params
-                      fake_update_response_.GetNoUpdateResponse(),
-                      -1,
-                      false,  // ping_only
-                      ErrorCode::kOmahaRequestXMLHasEntityDecl,
-                      metrics::CheckResult::kParsingError,
-                      metrics::CheckReaction::kUnset,
-                      metrics::DownloadErrorCode::kUnset,
-                      &response,
-                      nullptr));
+  ASSERT_FALSE(TestUpdateCheck(fake_update_response_.GetNoUpdateResponse(),
+                               -1,
+                               false,  // ping_only
+                               ErrorCode::kOmahaRequestXMLHasEntityDecl,
+                               metrics::CheckResult::kParsingError,
+                               metrics::CheckReaction::kUnset,
+                               metrics::DownloadErrorCode::kUnset,
+                               &response,
+                               nullptr));
   EXPECT_FALSE(response.update_exists);
 }
 
 TEST_F(OmahaRequestActionTest, NoUpdateTest) {
   OmahaResponse response;
-  ASSERT_TRUE(
-      TestUpdateCheck(nullptr,  // request_params
-                      fake_update_response_.GetNoUpdateResponse(),
-                      -1,
-                      false,  // ping_only
-                      ErrorCode::kSuccess,
-                      metrics::CheckResult::kNoUpdateAvailable,
-                      metrics::CheckReaction::kUnset,
-                      metrics::DownloadErrorCode::kUnset,
-                      &response,
-                      nullptr));
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetNoUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kNoUpdateAvailable,
+                              metrics::CheckReaction::kUnset,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
   EXPECT_FALSE(response.update_exists);
 }
 
 TEST_F(OmahaRequestActionTest, MultiAppNoUpdateTest) {
   OmahaResponse response;
   fake_update_response_.multi_app_no_update = true;
-  ASSERT_TRUE(TestUpdateCheck(nullptr,  // request_params
-                              fake_update_response_.GetNoUpdateResponse(),
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetNoUpdateResponse(),
                               -1,
                               false,  // ping_only
                               ErrorCode::kSuccess,
@@ -503,8 +598,7 @@
 TEST_F(OmahaRequestActionTest, MultiAppNoPartialUpdateTest) {
   OmahaResponse response;
   fake_update_response_.multi_app_no_update = true;
-  ASSERT_TRUE(TestUpdateCheck(nullptr,  // request_params
-                              fake_update_response_.GetUpdateResponse(),
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
                               -1,
                               false,  // ping_only
                               ErrorCode::kSuccess,
@@ -519,7 +613,6 @@
 TEST_F(OmahaRequestActionTest, NoSelfUpdateTest) {
   OmahaResponse response;
   ASSERT_TRUE(TestUpdateCheck(
-      nullptr,  // request_params
       "<response><app><updatecheck status=\"ok\"><manifest><actions><action "
       "event=\"postinstall\" noupdate=\"true\"/></actions>"
       "</manifest></updatecheck></app></response>",
@@ -539,17 +632,15 @@
 TEST_F(OmahaRequestActionTest, ValidUpdateTest) {
   OmahaResponse response;
   fake_update_response_.deadline = "20101020";
-  ASSERT_TRUE(
-      TestUpdateCheck(nullptr,  // request_params
-                      fake_update_response_.GetUpdateResponse(),
-                      -1,
-                      false,  // ping_only
-                      ErrorCode::kSuccess,
-                      metrics::CheckResult::kUpdateAvailable,
-                      metrics::CheckReaction::kUpdating,
-                      metrics::DownloadErrorCode::kUnset,
-                      &response,
-                      nullptr));
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kUpdateAvailable,
+                              metrics::CheckReaction::kUpdating,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
   EXPECT_TRUE(response.update_exists);
   EXPECT_EQ(fake_update_response_.version, response.version);
   EXPECT_EQ("", response.system_version);
@@ -572,8 +663,7 @@
 TEST_F(OmahaRequestActionTest, MultiPackageUpdateTest) {
   OmahaResponse response;
   fake_update_response_.multi_package = true;
-  ASSERT_TRUE(TestUpdateCheck(nullptr,  // request_params
-                              fake_update_response_.GetUpdateResponse(),
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
                               -1,
                               false,  // ping_only
                               ErrorCode::kSuccess,
@@ -602,8 +692,7 @@
 TEST_F(OmahaRequestActionTest, MultiAppUpdateTest) {
   OmahaResponse response;
   fake_update_response_.multi_app = true;
-  ASSERT_TRUE(TestUpdateCheck(nullptr,  // request_params
-                              fake_update_response_.GetUpdateResponse(),
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
                               -1,
                               false,  // ping_only
                               ErrorCode::kSuccess,
@@ -635,8 +724,7 @@
   // trigger the lining up of the app and system versions
   request_params_.set_system_app_id(fake_update_response_.app_id2);
 
-  ASSERT_TRUE(TestUpdateCheck(nullptr,  // request_params
-                              fake_update_response_.GetUpdateResponse(),
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
                               -1,
                               false,  // ping_only
                               ErrorCode::kSuccess,
@@ -667,8 +755,7 @@
   OmahaResponse response;
   fake_update_response_.multi_app = true;
   fake_update_response_.multi_app_self_update = true;
-  ASSERT_TRUE(TestUpdateCheck(nullptr,  // request_params
-                              fake_update_response_.GetUpdateResponse(),
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
                               -1,
                               false,  // ping_only
                               ErrorCode::kSuccess,
@@ -696,8 +783,7 @@
   OmahaResponse response;
   fake_update_response_.multi_app = true;
   fake_update_response_.multi_package = true;
-  ASSERT_TRUE(TestUpdateCheck(nullptr,  // request_params
-                              fake_update_response_.GetUpdateResponse(),
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
                               -1,
                               false,  // ping_only
                               ErrorCode::kSuccess,
@@ -733,8 +819,7 @@
 TEST_F(OmahaRequestActionTest, PowerwashTest) {
   OmahaResponse response;
   fake_update_response_.powerwash = true;
-  ASSERT_TRUE(TestUpdateCheck(nullptr,  // request_params
-                              fake_update_response_.GetUpdateResponse(),
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
                               -1,
                               false,  // ping_only
                               ErrorCode::kSuccess,
@@ -747,31 +832,36 @@
   EXPECT_TRUE(response.powerwash_required);
 }
 
-TEST_F(OmahaRequestActionTest, ExtraHeadersSentTest) {
-  const string http_response = "<?xml invalid response";
+TEST_F(OmahaRequestActionTest, ExtraHeadersSentInteractiveTest) {
+  OmahaResponse response;
   request_params_.set_interactive(true);
+  test_http_fetcher_headers_ = true;
+  ASSERT_FALSE(TestUpdateCheck("invalid xml>",
+                               -1,
+                               false,  // ping_only
+                               ErrorCode::kOmahaRequestXMLParseError,
+                               metrics::CheckResult::kParsingError,
+                               metrics::CheckReaction::kUnset,
+                               metrics::DownloadErrorCode::kUnset,
+                               &response,
+                               nullptr));
+  EXPECT_FALSE(response.update_exists);
+}
 
-  brillo::FakeMessageLoop loop(nullptr);
-  loop.SetAsCurrent();
-
-  MockHttpFetcher* fetcher =
-      new MockHttpFetcher(http_response.data(), http_response.size(), nullptr);
-  OmahaRequestAction action(&fake_system_state_, nullptr,
-                            base::WrapUnique(fetcher), false);
-  ActionProcessor processor;
-  processor.EnqueueAction(&action);
-
-  loop.PostTask(base::Bind(
-      [](ActionProcessor* processor) { processor->StartProcessing(); },
-      base::Unretained(&processor)));
-  loop.Run();
-  EXPECT_FALSE(loop.PendingTasks());
-
-  // Check that the headers were set in the fetcher during the action. Note that
-  // we set this request as "interactive".
-  EXPECT_EQ("fg", fetcher->GetHeader("X-Goog-Update-Interactivity"));
-  EXPECT_EQ(kTestAppId, fetcher->GetHeader("X-Goog-Update-AppId"));
-  EXPECT_NE("", fetcher->GetHeader("X-Goog-Update-Updater"));
+TEST_F(OmahaRequestActionTest, ExtraHeadersSentNoInteractiveTest) {
+  OmahaResponse response;
+  request_params_.set_interactive(false);
+  test_http_fetcher_headers_ = true;
+  ASSERT_FALSE(TestUpdateCheck("invalid xml>",
+                               -1,
+                               false,  // ping_only
+                               ErrorCode::kOmahaRequestXMLParseError,
+                               metrics::CheckResult::kParsingError,
+                               metrics::CheckReaction::kUnset,
+                               metrics::DownloadErrorCode::kUnset,
+                               &response,
+                               nullptr));
+  EXPECT_FALSE(response.update_exists);
 }
 
 TEST_F(OmahaRequestActionTest, ValidUpdateBlockedByConnection) {
@@ -789,20 +879,182 @@
   EXPECT_CALL(mock_cm, IsUpdateAllowedOver(ConnectionType::kEthernet, _))
       .WillRepeatedly(Return(false));
 
-  ASSERT_FALSE(
-      TestUpdateCheck(nullptr,  // request_params
-                      fake_update_response_.GetUpdateResponse(),
-                      -1,
-                      false,  // ping_only
-                      ErrorCode::kOmahaUpdateIgnoredPerPolicy,
-                      metrics::CheckResult::kUpdateAvailable,
-                      metrics::CheckReaction::kIgnored,
-                      metrics::DownloadErrorCode::kUnset,
-                      &response,
-                      nullptr));
+  ASSERT_FALSE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
+                               -1,
+                               false,  // ping_only
+                               ErrorCode::kOmahaUpdateIgnoredPerPolicy,
+                               metrics::CheckResult::kUpdateAvailable,
+                               metrics::CheckReaction::kIgnored,
+                               metrics::DownloadErrorCode::kUnset,
+                               &response,
+                               nullptr));
   EXPECT_FALSE(response.update_exists);
 }
 
+TEST_F(OmahaRequestActionTest, ValidUpdateOverCellularAllowedByDevicePolicy) {
+  // This test tests that update over cellular is allowed as device policy
+  // says yes.
+  OmahaResponse response;
+  MockConnectionManager mock_cm;
+
+  fake_system_state_.set_connection_manager(&mock_cm);
+
+  EXPECT_CALL(mock_cm, GetConnectionProperties(_, _))
+      .WillRepeatedly(DoAll(SetArgPointee<0>(ConnectionType::kCellular),
+                            SetArgPointee<1>(ConnectionTethering::kUnknown),
+                            Return(true)));
+  EXPECT_CALL(mock_cm, IsAllowedConnectionTypesForUpdateSet())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(mock_cm, IsUpdateAllowedOver(ConnectionType::kCellular, _))
+      .WillRepeatedly(Return(true));
+
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kUpdateAvailable,
+                              metrics::CheckReaction::kUpdating,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
+  EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, ValidUpdateOverCellularBlockedByDevicePolicy) {
+  // This test tests that update over cellular is blocked as device policy
+  // says no.
+  OmahaResponse response;
+  MockConnectionManager mock_cm;
+
+  fake_system_state_.set_connection_manager(&mock_cm);
+
+  EXPECT_CALL(mock_cm, GetConnectionProperties(_, _))
+      .WillRepeatedly(DoAll(SetArgPointee<0>(ConnectionType::kCellular),
+                            SetArgPointee<1>(ConnectionTethering::kUnknown),
+                            Return(true)));
+  EXPECT_CALL(mock_cm, IsAllowedConnectionTypesForUpdateSet())
+      .WillRepeatedly(Return(true));
+  EXPECT_CALL(mock_cm, IsUpdateAllowedOver(ConnectionType::kCellular, _))
+      .WillRepeatedly(Return(false));
+
+  ASSERT_FALSE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
+                               -1,
+                               false,  // ping_only
+                               ErrorCode::kOmahaUpdateIgnoredPerPolicy,
+                               metrics::CheckResult::kUpdateAvailable,
+                               metrics::CheckReaction::kIgnored,
+                               metrics::DownloadErrorCode::kUnset,
+                               &response,
+                               nullptr));
+  EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest,
+       ValidUpdateOverCellularAllowedByUserPermissionTrue) {
+  // This test tests that, when device policy is not set, update over cellular
+  // is allowed as permission for update over cellular is set to true.
+  OmahaResponse response;
+  MockConnectionManager mock_cm;
+
+  fake_prefs_.SetBoolean(kPrefsUpdateOverCellularPermission, true);
+  fake_system_state_.set_connection_manager(&mock_cm);
+
+  EXPECT_CALL(mock_cm, GetConnectionProperties(_, _))
+      .WillRepeatedly(DoAll(SetArgPointee<0>(ConnectionType::kCellular),
+                            SetArgPointee<1>(ConnectionTethering::kUnknown),
+                            Return(true)));
+  EXPECT_CALL(mock_cm, IsAllowedConnectionTypesForUpdateSet())
+      .WillRepeatedly(Return(false));
+  EXPECT_CALL(mock_cm, IsUpdateAllowedOver(ConnectionType::kCellular, _))
+      .WillRepeatedly(Return(true));
+
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kUpdateAvailable,
+                              metrics::CheckReaction::kUpdating,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
+  EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest,
+       ValidUpdateOverCellularBlockedByUpdateTargetNotMatch) {
+  // This test tests that, when device policy is not set and permission for
+  // update over cellular is set to false or does not exist, update over
+  // cellular is blocked as update target does not match the omaha response.
+  OmahaResponse response;
+  MockConnectionManager mock_cm;
+  // A version different from the version in omaha response.
+  string diff_version = "99.99.99";
+  // A size different from the size in omaha response.
+  int64_t diff_size = 999;
+
+  fake_prefs_.SetString(kPrefsUpdateOverCellularTargetVersion, diff_version);
+  fake_prefs_.SetInt64(kPrefsUpdateOverCellularTargetSize, diff_size);
+  // This test tests cellular (3G) being the only connection type being allowed.
+  fake_system_state_.set_connection_manager(&mock_cm);
+
+  EXPECT_CALL(mock_cm, GetConnectionProperties(_, _))
+      .WillRepeatedly(DoAll(SetArgPointee<0>(ConnectionType::kCellular),
+                            SetArgPointee<1>(ConnectionTethering::kUnknown),
+                            Return(true)));
+  EXPECT_CALL(mock_cm, IsAllowedConnectionTypesForUpdateSet())
+      .WillRepeatedly(Return(false));
+  EXPECT_CALL(mock_cm, IsUpdateAllowedOver(ConnectionType::kCellular, _))
+      .WillRepeatedly(Return(true));
+
+  ASSERT_FALSE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
+                               -1,
+                               false,  // ping_only
+                               ErrorCode::kOmahaUpdateIgnoredOverCellular,
+                               metrics::CheckResult::kUpdateAvailable,
+                               metrics::CheckReaction::kIgnored,
+                               metrics::DownloadErrorCode::kUnset,
+                               &response,
+                               nullptr));
+  EXPECT_FALSE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest,
+       ValidUpdateOverCellularAllowedByUpdateTargetMatch) {
+  // This test tests that, when device policy is not set and permission for
+  // update over cellular is set to false or does not exist, update over
+  // cellular is allowed as update target matches the omaha response.
+  OmahaResponse response;
+  MockConnectionManager mock_cm;
+  // A version same as the version in omaha response.
+  string new_version = fake_update_response_.version;
+  // A size same as the size in omaha response.
+  int64_t new_size = fake_update_response_.size;
+
+  fake_prefs_.SetString(kPrefsUpdateOverCellularTargetVersion, new_version);
+  fake_prefs_.SetInt64(kPrefsUpdateOverCellularTargetSize, new_size);
+  fake_system_state_.set_connection_manager(&mock_cm);
+
+  EXPECT_CALL(mock_cm, GetConnectionProperties(_, _))
+      .WillRepeatedly(DoAll(SetArgPointee<0>(ConnectionType::kCellular),
+                            SetArgPointee<1>(ConnectionTethering::kUnknown),
+                            Return(true)));
+  EXPECT_CALL(mock_cm, IsAllowedConnectionTypesForUpdateSet())
+      .WillRepeatedly(Return(false));
+  EXPECT_CALL(mock_cm, IsUpdateAllowedOver(ConnectionType::kCellular, _))
+      .WillRepeatedly(Return(true));
+
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kUpdateAvailable,
+                              metrics::CheckReaction::kUpdating,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
+  EXPECT_TRUE(response.update_exists);
+}
+
 TEST_F(OmahaRequestActionTest, ValidUpdateBlockedByRollback) {
   string rollback_version = "1234.0.0";
   OmahaResponse response;
@@ -814,30 +1066,27 @@
     .WillRepeatedly(Return(rollback_version));
 
   fake_update_response_.version = rollback_version;
-  ASSERT_FALSE(
-      TestUpdateCheck(nullptr,  // request_params
-                      fake_update_response_.GetUpdateResponse(),
-                      -1,
-                      false,  // ping_only
-                      ErrorCode::kOmahaUpdateIgnoredPerPolicy,
-                      metrics::CheckResult::kUpdateAvailable,
-                      metrics::CheckReaction::kIgnored,
-                      metrics::DownloadErrorCode::kUnset,
-                      &response,
-                      nullptr));
+  ASSERT_FALSE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
+                               -1,
+                               false,  // ping_only
+                               ErrorCode::kOmahaUpdateIgnoredPerPolicy,
+                               metrics::CheckResult::kUpdateAvailable,
+                               metrics::CheckReaction::kIgnored,
+                               metrics::DownloadErrorCode::kUnset,
+                               &response,
+                               nullptr));
   EXPECT_FALSE(response.update_exists);
 }
 
-// Verify that update checks called during OOBE will only try to download
-// an update if the response includes a non-empty deadline field.
+// Verify that update checks called during OOBE will not try to download an
+// update if the response doesn't include the deadline field.
 TEST_F(OmahaRequestActionTest, SkipNonCriticalUpdatesBeforeOOBE) {
   OmahaResponse response;
+  fake_system_state_.fake_hardware()->UnsetIsOOBEComplete();
 
   // TODO(senj): set better default value for metrics::checkresult in
   // OmahaRequestAction::ActionCompleted.
-  fake_system_state_.fake_hardware()->UnsetIsOOBEComplete();
-  ASSERT_FALSE(TestUpdateCheck(nullptr,  // request_params
-                               fake_update_response_.GetUpdateResponse(),
+  ASSERT_FALSE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
                                -1,
                                false,  // ping_only
                                ErrorCode::kNonCriticalUpdateInOOBE,
@@ -847,11 +1096,16 @@
                                &response,
                                nullptr));
   EXPECT_FALSE(response.update_exists);
+}
 
-  // The IsOOBEComplete() value is ignored when the OOBE flow is not enabled.
+// Verify that the IsOOBEComplete() value is ignored when the OOBE flow is not
+// enabled.
+TEST_F(OmahaRequestActionTest, SkipNonCriticalUpdatesBeforeOOBEDisabled) {
+  OmahaResponse response;
+  fake_system_state_.fake_hardware()->UnsetIsOOBEComplete();
   fake_system_state_.fake_hardware()->SetIsOOBEEnabled(false);
-  ASSERT_TRUE(TestUpdateCheck(nullptr,  // request_params
-                              fake_update_response_.GetUpdateResponse(),
+
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
                               -1,
                               false,  // ping_only
                               ErrorCode::kSuccess,
@@ -861,122 +1115,131 @@
                               &response,
                               nullptr));
   EXPECT_TRUE(response.update_exists);
-  fake_system_state_.fake_hardware()->SetIsOOBEEnabled(true);
+}
 
-  // The payload is applied when a deadline was set in the response.
+// Verify that update checks called during OOBE will still try to download an
+// update if the response includes the deadline field.
+TEST_F(OmahaRequestActionTest, SkipNonCriticalUpdatesBeforeOOBEDeadlineSet) {
+  OmahaResponse response;
+  fake_system_state_.fake_hardware()->UnsetIsOOBEComplete();
   fake_update_response_.deadline = "20101020";
-  ASSERT_TRUE(
-      TestUpdateCheck(nullptr,  // request_params
-                      fake_update_response_.GetUpdateResponse(),
-                      -1,
-                      false,  // ping_only
-                      ErrorCode::kSuccess,
-                      metrics::CheckResult::kUpdateAvailable,
-                      metrics::CheckReaction::kUpdating,
-                      metrics::DownloadErrorCode::kUnset,
-                      &response,
-                      nullptr));
+
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kUpdateAvailable,
+                              metrics::CheckReaction::kUpdating,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
   EXPECT_TRUE(response.update_exists);
 }
 
+// Verify that update checks called during OOBE will not try to download an
+// update if a rollback happened, even when the response includes the deadline
+// field.
+TEST_F(OmahaRequestActionTest, SkipNonCriticalUpdatesBeforeOOBERollback) {
+  OmahaResponse response;
+  fake_system_state_.fake_hardware()->UnsetIsOOBEComplete();
+  fake_update_response_.deadline = "20101020";
+  EXPECT_CALL(*(fake_system_state_.mock_payload_state()), GetRollbackHappened())
+      .WillOnce(Return(true));
+
+  ASSERT_FALSE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
+                               -1,
+                               false,  // ping_only
+                               ErrorCode::kNonCriticalUpdateInOOBE,
+                               metrics::CheckResult::kParsingError,
+                               metrics::CheckReaction::kUnset,
+                               metrics::DownloadErrorCode::kUnset,
+                               &response,
+                               nullptr));
+  EXPECT_FALSE(response.update_exists);
+}
+
 TEST_F(OmahaRequestActionTest, WallClockBasedWaitAloneCausesScattering) {
   OmahaResponse response;
-  OmahaRequestParams params = request_params_;
-  params.set_wall_clock_based_wait_enabled(true);
-  params.set_update_check_count_wait_enabled(false);
-  params.set_waiting_period(TimeDelta::FromDays(2));
+  request_params_.set_wall_clock_based_wait_enabled(true);
+  request_params_.set_update_check_count_wait_enabled(false);
+  request_params_.set_waiting_period(TimeDelta::FromDays(2));
 
-  ASSERT_FALSE(
-      TestUpdateCheck(&params,
-                      fake_update_response_.GetUpdateResponse(),
-                      -1,
-                      false,  // ping_only
-                      ErrorCode::kOmahaUpdateDeferredPerPolicy,
-                      metrics::CheckResult::kUpdateAvailable,
-                      metrics::CheckReaction::kDeferring,
-                      metrics::DownloadErrorCode::kUnset,
-                      &response,
-                      nullptr));
+  ASSERT_FALSE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
+                               -1,
+                               false,  // ping_only
+                               ErrorCode::kOmahaUpdateDeferredPerPolicy,
+                               metrics::CheckResult::kUpdateAvailable,
+                               metrics::CheckReaction::kDeferring,
+                               metrics::DownloadErrorCode::kUnset,
+                               &response,
+                               nullptr));
   EXPECT_FALSE(response.update_exists);
 
   // Verify if we are interactive check we don't defer.
-  params.set_interactive(true);
-  ASSERT_TRUE(
-      TestUpdateCheck(&params,
-                      fake_update_response_.GetUpdateResponse(),
-                      -1,
-                      false,  // ping_only
-                      ErrorCode::kSuccess,
-                      metrics::CheckResult::kUpdateAvailable,
-                      metrics::CheckReaction::kUpdating,
-                      metrics::DownloadErrorCode::kUnset,
-                      &response,
-                      nullptr));
+  request_params_.set_interactive(true);
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kUpdateAvailable,
+                              metrics::CheckReaction::kUpdating,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
   EXPECT_TRUE(response.update_exists);
 }
 
 TEST_F(OmahaRequestActionTest, NoWallClockBasedWaitCausesNoScattering) {
   OmahaResponse response;
-  OmahaRequestParams params = request_params_;
-  params.set_wall_clock_based_wait_enabled(false);
-  params.set_waiting_period(TimeDelta::FromDays(2));
+  request_params_.set_wall_clock_based_wait_enabled(false);
+  request_params_.set_waiting_period(TimeDelta::FromDays(2));
+  request_params_.set_update_check_count_wait_enabled(true);
+  request_params_.set_min_update_checks_needed(1);
+  request_params_.set_max_update_checks_allowed(8);
 
-  params.set_update_check_count_wait_enabled(true);
-  params.set_min_update_checks_needed(1);
-  params.set_max_update_checks_allowed(8);
-
-  ASSERT_TRUE(
-      TestUpdateCheck(&params,
-                      fake_update_response_.GetUpdateResponse(),
-                      -1,
-                      false,  // ping_only
-                      ErrorCode::kSuccess,
-                      metrics::CheckResult::kUpdateAvailable,
-                      metrics::CheckReaction::kUpdating,
-                      metrics::DownloadErrorCode::kUnset,
-                      &response,
-                      nullptr));
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kUpdateAvailable,
+                              metrics::CheckReaction::kUpdating,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
   EXPECT_TRUE(response.update_exists);
 }
 
 TEST_F(OmahaRequestActionTest, ZeroMaxDaysToScatterCausesNoScattering) {
   OmahaResponse response;
-  OmahaRequestParams params = request_params_;
-  params.set_wall_clock_based_wait_enabled(true);
-  params.set_waiting_period(TimeDelta::FromDays(2));
-
-  params.set_update_check_count_wait_enabled(true);
-  params.set_min_update_checks_needed(1);
-  params.set_max_update_checks_allowed(8);
+  request_params_.set_wall_clock_based_wait_enabled(true);
+  request_params_.set_waiting_period(TimeDelta::FromDays(2));
+  request_params_.set_update_check_count_wait_enabled(true);
+  request_params_.set_min_update_checks_needed(1);
+  request_params_.set_max_update_checks_allowed(8);
 
   fake_update_response_.max_days_to_scatter = "0";
-  ASSERT_TRUE(
-      TestUpdateCheck(&params,
-                      fake_update_response_.GetUpdateResponse(),
-                      -1,
-                      false,  // ping_only
-                      ErrorCode::kSuccess,
-                      metrics::CheckResult::kUpdateAvailable,
-                      metrics::CheckReaction::kUpdating,
-                      metrics::DownloadErrorCode::kUnset,
-                      &response,
-                      nullptr));
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kUpdateAvailable,
+                              metrics::CheckReaction::kUpdating,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
   EXPECT_TRUE(response.update_exists);
 }
 
 
 TEST_F(OmahaRequestActionTest, ZeroUpdateCheckCountCausesNoScattering) {
   OmahaResponse response;
-  OmahaRequestParams params = request_params_;
-  params.set_wall_clock_based_wait_enabled(true);
-  params.set_waiting_period(TimeDelta());
-
-  params.set_update_check_count_wait_enabled(true);
-  params.set_min_update_checks_needed(0);
-  params.set_max_update_checks_allowed(0);
+  request_params_.set_wall_clock_based_wait_enabled(true);
+  request_params_.set_waiting_period(TimeDelta());
+  request_params_.set_update_check_count_wait_enabled(true);
+  request_params_.set_min_update_checks_needed(0);
+  request_params_.set_max_update_checks_allowed(0);
 
   ASSERT_TRUE(TestUpdateCheck(
-                      &params,
                       fake_update_response_.GetUpdateResponse(),
                       -1,
                       false,  // ping_only
@@ -995,16 +1258,13 @@
 
 TEST_F(OmahaRequestActionTest, NonZeroUpdateCheckCountCausesScattering) {
   OmahaResponse response;
-  OmahaRequestParams params = request_params_;
-  params.set_wall_clock_based_wait_enabled(true);
-  params.set_waiting_period(TimeDelta());
-
-  params.set_update_check_count_wait_enabled(true);
-  params.set_min_update_checks_needed(1);
-  params.set_max_update_checks_allowed(8);
+  request_params_.set_wall_clock_based_wait_enabled(true);
+  request_params_.set_waiting_period(TimeDelta());
+  request_params_.set_update_check_count_wait_enabled(true);
+  request_params_.set_min_update_checks_needed(1);
+  request_params_.set_max_update_checks_allowed(8);
 
   ASSERT_FALSE(TestUpdateCheck(
-                      &params,
                       fake_update_response_.GetUpdateResponse(),
                       -1,
                       false,  // ping_only
@@ -1021,35 +1281,30 @@
   EXPECT_FALSE(response.update_exists);
 
   // Verify if we are interactive check we don't defer.
-  params.set_interactive(true);
-  ASSERT_TRUE(
-      TestUpdateCheck(&params,
-                      fake_update_response_.GetUpdateResponse(),
-                      -1,
-                      false,  // ping_only
-                      ErrorCode::kSuccess,
-                      metrics::CheckResult::kUpdateAvailable,
-                      metrics::CheckReaction::kUpdating,
-                      metrics::DownloadErrorCode::kUnset,
-                      &response,
-                      nullptr));
+  request_params_.set_interactive(true);
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kUpdateAvailable,
+                              metrics::CheckReaction::kUpdating,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
   EXPECT_TRUE(response.update_exists);
 }
 
 TEST_F(OmahaRequestActionTest, ExistingUpdateCheckCountCausesScattering) {
   OmahaResponse response;
-  OmahaRequestParams params = request_params_;
-  params.set_wall_clock_based_wait_enabled(true);
-  params.set_waiting_period(TimeDelta());
-
-  params.set_update_check_count_wait_enabled(true);
-  params.set_min_update_checks_needed(1);
-  params.set_max_update_checks_allowed(8);
+  request_params_.set_wall_clock_based_wait_enabled(true);
+  request_params_.set_waiting_period(TimeDelta());
+  request_params_.set_update_check_count_wait_enabled(true);
+  request_params_.set_min_update_checks_needed(1);
+  request_params_.set_max_update_checks_allowed(8);
 
   ASSERT_TRUE(fake_prefs_.SetInt64(kPrefsUpdateCheckCount, 5));
 
   ASSERT_FALSE(TestUpdateCheck(
-                      &params,
                       fake_update_response_.GetUpdateResponse(),
                       -1,
                       false,  // ping_only
@@ -1068,31 +1323,63 @@
   EXPECT_FALSE(response.update_exists);
 
   // Verify if we are interactive check we don't defer.
-  params.set_interactive(true);
-  ASSERT_TRUE(
-      TestUpdateCheck(&params,
-                      fake_update_response_.GetUpdateResponse(),
-                      -1,
-                      false,  // ping_only
-                      ErrorCode::kSuccess,
-                      metrics::CheckResult::kUpdateAvailable,
-                      metrics::CheckReaction::kUpdating,
-                      metrics::DownloadErrorCode::kUnset,
-                      &response,
-                      nullptr));
+  request_params_.set_interactive(true);
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kUpdateAvailable,
+                              metrics::CheckReaction::kUpdating,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
+  EXPECT_TRUE(response.update_exists);
+}
+
+TEST_F(OmahaRequestActionTest, StagingTurnedOnCausesScattering) {
+  // If staging is on, the value for max days to scatter should be ignored, and
+  // staging's scatter value should be used.
+  OmahaResponse response;
+  request_params_.set_wall_clock_based_wait_enabled(true);
+  request_params_.set_waiting_period(TimeDelta::FromDays(6));
+  request_params_.set_update_check_count_wait_enabled(false);
+
+  ASSERT_TRUE(fake_prefs_.SetInt64(kPrefsWallClockStagingWaitPeriod, 6));
+  // This should not prevent scattering due to staging.
+  fake_update_response_.max_days_to_scatter = "0";
+  ASSERT_FALSE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
+                               -1,
+                               false,  // ping_only
+                               ErrorCode::kOmahaUpdateDeferredPerPolicy,
+                               metrics::CheckResult::kUpdateAvailable,
+                               metrics::CheckReaction::kDeferring,
+                               metrics::DownloadErrorCode::kUnset,
+                               &response,
+                               nullptr));
+  EXPECT_FALSE(response.update_exists);
+
+  // Interactive updates should not be affected.
+  request_params_.set_interactive(true);
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kUpdateAvailable,
+                              metrics::CheckReaction::kUpdating,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
   EXPECT_TRUE(response.update_exists);
 }
 
 TEST_F(OmahaRequestActionTest, CohortsArePersisted) {
   OmahaResponse response;
-  OmahaRequestParams params = request_params_;
   fake_update_response_.include_cohorts = true;
   fake_update_response_.cohort = "s/154454/8479665";
   fake_update_response_.cohorthint = "please-put-me-on-beta";
   fake_update_response_.cohortname = "stable";
 
-  ASSERT_TRUE(TestUpdateCheck(&params,
-                              fake_update_response_.GetUpdateResponse(),
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
                               -1,
                               false,  // ping_only
                               ErrorCode::kSuccess,
@@ -1115,7 +1402,6 @@
 
 TEST_F(OmahaRequestActionTest, CohortsAreUpdated) {
   OmahaResponse response;
-  OmahaRequestParams params = request_params_;
   EXPECT_TRUE(fake_prefs_.SetString(kPrefsOmahaCohort, "old_value"));
   EXPECT_TRUE(fake_prefs_.SetString(kPrefsOmahaCohortHint, "old_hint"));
   EXPECT_TRUE(fake_prefs_.SetString(kPrefsOmahaCohortName, "old_name"));
@@ -1124,8 +1410,7 @@
   fake_update_response_.cohorthint = "please-put-me-on-beta";
   fake_update_response_.cohortname = "";
 
-  ASSERT_TRUE(TestUpdateCheck(&params,
-                              fake_update_response_.GetUpdateResponse(),
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
                               -1,
                               false,  // ping_only
                               ErrorCode::kSuccess,
@@ -1147,11 +1432,9 @@
 
 TEST_F(OmahaRequestActionTest, CohortsAreNotModifiedWhenMissing) {
   OmahaResponse response;
-  OmahaRequestParams params = request_params_;
   EXPECT_TRUE(fake_prefs_.SetString(kPrefsOmahaCohort, "old_value"));
 
-  ASSERT_TRUE(TestUpdateCheck(&params,
-                              fake_update_response_.GetUpdateResponse(),
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
                               -1,
                               false,  // ping_only
                               ErrorCode::kSuccess,
@@ -1171,14 +1454,12 @@
 
 TEST_F(OmahaRequestActionTest, CohortsArePersistedWhenNoUpdate) {
   OmahaResponse response;
-  OmahaRequestParams params = request_params_;
   fake_update_response_.include_cohorts = true;
   fake_update_response_.cohort = "s/154454/8479665";
   fake_update_response_.cohorthint = "please-put-me-on-beta";
   fake_update_response_.cohortname = "stable";
 
-  ASSERT_TRUE(TestUpdateCheck(&params,
-                              fake_update_response_.GetNoUpdateResponse(),
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetNoUpdateResponse(),
                               -1,
                               false,  // ping_only
                               ErrorCode::kSuccess,
@@ -1201,15 +1482,13 @@
 
 TEST_F(OmahaRequestActionTest, MultiAppCohortTest) {
   OmahaResponse response;
-  OmahaRequestParams params = request_params_;
   fake_update_response_.multi_app = true;
   fake_update_response_.include_cohorts = true;
   fake_update_response_.cohort = "s/154454/8479665";
   fake_update_response_.cohorthint = "please-put-me-on-beta";
   fake_update_response_.cohortname = "stable";
 
-  ASSERT_TRUE(TestUpdateCheck(&params,
-                              fake_update_response_.GetUpdateResponse(),
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
                               -1,
                               false,  // ping_only
                               ErrorCode::kSuccess,
@@ -1236,19 +1515,15 @@
   brillo::FakeMessageLoop loop(nullptr);
   loop.SetAsCurrent();
 
-  OmahaRequestParams params = request_params_;
-  fake_system_state_.set_request_params(&params);
-  OmahaRequestAction action(
+  auto action = std::make_unique<OmahaRequestAction>(
       &fake_system_state_,
       nullptr,
-      std::make_unique<MockHttpFetcher>(http_response.data(),
-                                        http_response.size(),
-                                        nullptr),
+      std::make_unique<MockHttpFetcher>(
+          http_response.data(), http_response.size(), nullptr),
       false);
-  OmahaRequestActionTestProcessorDelegate delegate;
   ActionProcessor processor;
-  processor.set_delegate(&delegate);
-  processor.EnqueueAction(&action);
+  processor.set_delegate(&delegate_);
+  processor.EnqueueAction(std::move(action));
 
   loop.PostTask(base::Bind(
       [](ActionProcessor* processor) { processor->StartProcessing(); },
@@ -1260,40 +1535,35 @@
 
 TEST_F(OmahaRequestActionTest, InvalidXmlTest) {
   OmahaResponse response;
-  ASSERT_FALSE(
-      TestUpdateCheck(nullptr,  // request_params
-                      "invalid xml>",
-                      -1,
-                      false,  // ping_only
-                      ErrorCode::kOmahaRequestXMLParseError,
-                      metrics::CheckResult::kParsingError,
-                      metrics::CheckReaction::kUnset,
-                      metrics::DownloadErrorCode::kUnset,
-                      &response,
-                      nullptr));
+  ASSERT_FALSE(TestUpdateCheck("invalid xml>",
+                               -1,
+                               false,  // ping_only
+                               ErrorCode::kOmahaRequestXMLParseError,
+                               metrics::CheckResult::kParsingError,
+                               metrics::CheckReaction::kUnset,
+                               metrics::DownloadErrorCode::kUnset,
+                               &response,
+                               nullptr));
   EXPECT_FALSE(response.update_exists);
 }
 
 TEST_F(OmahaRequestActionTest, EmptyResponseTest) {
   OmahaResponse response;
-  ASSERT_FALSE(
-      TestUpdateCheck(nullptr,  // request_params
-                      "",
-                      -1,
-                      false,  // ping_only
-                      ErrorCode::kOmahaRequestEmptyResponseError,
-                      metrics::CheckResult::kParsingError,
-                      metrics::CheckReaction::kUnset,
-                      metrics::DownloadErrorCode::kUnset,
-                      &response,
-                      nullptr));
+  ASSERT_FALSE(TestUpdateCheck("",
+                               -1,
+                               false,  // ping_only
+                               ErrorCode::kOmahaRequestEmptyResponseError,
+                               metrics::CheckResult::kParsingError,
+                               metrics::CheckReaction::kUnset,
+                               metrics::DownloadErrorCode::kUnset,
+                               &response,
+                               nullptr));
   EXPECT_FALSE(response.update_exists);
 }
 
 TEST_F(OmahaRequestActionTest, MissingStatusTest) {
   OmahaResponse response;
   ASSERT_FALSE(TestUpdateCheck(
-      nullptr,  // request_params
       "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response protocol=\"3.0\">"
       "<daystart elapsed_seconds=\"100\"/>"
       "<app appid=\"foo\" status=\"ok\">"
@@ -1313,7 +1583,6 @@
 TEST_F(OmahaRequestActionTest, InvalidStatusTest) {
   OmahaResponse response;
   ASSERT_FALSE(TestUpdateCheck(
-      nullptr,  // request_params
       "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response protocol=\"3.0\">"
       "<daystart elapsed_seconds=\"100\"/>"
       "<app appid=\"foo\" status=\"ok\">"
@@ -1333,7 +1602,6 @@
 TEST_F(OmahaRequestActionTest, MissingNodesetTest) {
   OmahaResponse response;
   ASSERT_FALSE(TestUpdateCheck(
-      nullptr,  // request_params
       "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response protocol=\"3.0\">"
       "<daystart elapsed_seconds=\"100\"/>"
       "<app appid=\"foo\" status=\"ok\">"
@@ -1374,8 +1642,7 @@
   LOG(INFO) << "Input Response = " << input_response;
 
   OmahaResponse response;
-  ASSERT_TRUE(TestUpdateCheck(nullptr,  // request_params
-                              input_response,
+  ASSERT_TRUE(TestUpdateCheck(input_response,
                               -1,
                               false,  // ping_only
                               ErrorCode::kSuccess,
@@ -1415,17 +1682,16 @@
   loop.SetAsCurrent();
 
   string http_response("doesn't matter");
-  OmahaRequestAction action(
+  auto action = std::make_unique<OmahaRequestAction>(
       &fake_system_state_,
       nullptr,
-      std::make_unique<MockHttpFetcher>(http_response.data(),
-                                        http_response.size(),
-                                        nullptr),
+      std::make_unique<MockHttpFetcher>(
+          http_response.data(), http_response.size(), nullptr),
       false);
   TerminateEarlyTestProcessorDelegate delegate;
   ActionProcessor processor;
   processor.set_delegate(&delegate);
-  processor.EnqueueAction(&action);
+  processor.EnqueueAction(std::move(action));
 
   loop.PostTask(base::Bind(&TerminateTransferTestStarter, &processor));
   loop.Run();
@@ -1457,39 +1723,26 @@
   brillo::Blob post_data;
 
   // Make sure XML Encode is being called on the params
-  OmahaRequestParams params(&fake_system_state_,
-                            constants::kOmahaPlatformName,
-                            OmahaRequestParams::kOsVersion,
-                            "testtheservice_pack>",
-                            "x86 generic<id",
-                            kTestAppId,
-                            "0.1.0.0",
-                            "en-US",
-                            "unittest_track&lt;",
-                            "<OEM MODEL>",
-                            "ChromeOSFirmware.1.0",
-                            "EC100",
-                            false,   // delta okay
-                            false,   // interactive
-                            "http://url",
-                            "");     // target_version_prefix
+  request_params_.set_os_sp("testtheservice_pack>");
+  request_params_.set_os_board("x86 generic<id");
+  request_params_.set_current_channel("unittest_track&lt;");
+  request_params_.set_target_channel("unittest_track&lt;");
+  request_params_.set_hwid("<OEM MODEL>");
   fake_prefs_.SetString(kPrefsOmahaCohort, "evil\nstring");
   fake_prefs_.SetString(kPrefsOmahaCohortHint, "evil&string\\");
   fake_prefs_.SetString(kPrefsOmahaCohortName,
                         base::JoinString(
                             vector<string>(100, "My spoon is too big."), " "));
   OmahaResponse response;
-  ASSERT_FALSE(
-      TestUpdateCheck(&params,
-                      "invalid xml>",
-                      -1,
-                      false,  // ping_only
-                      ErrorCode::kOmahaRequestXMLParseError,
-                      metrics::CheckResult::kParsingError,
-                      metrics::CheckReaction::kUnset,
-                      metrics::DownloadErrorCode::kUnset,
-                      &response,
-                      &post_data));
+  ASSERT_FALSE(TestUpdateCheck("invalid xml>",
+                               -1,
+                               false,  // ping_only
+                               ErrorCode::kOmahaRequestXMLParseError,
+                               metrics::CheckResult::kParsingError,
+                               metrics::CheckReaction::kUnset,
+                               metrics::DownloadErrorCode::kUnset,
+                               &response,
+                               &post_data));
   // convert post_data to string
   string post_str(post_data.begin(), post_data.end());
   EXPECT_NE(string::npos, post_str.find("testtheservice_pack&gt;"));
@@ -1513,17 +1766,15 @@
   fake_update_response_.deadline = "&lt;20110101";
   fake_update_response_.more_info_url = "testthe&lt;url";
   fake_update_response_.codebase = "testthe&amp;codebase/";
-  ASSERT_TRUE(
-      TestUpdateCheck(nullptr,  // request_params
-                      fake_update_response_.GetUpdateResponse(),
-                      -1,
-                      false,  // ping_only
-                      ErrorCode::kSuccess,
-                      metrics::CheckResult::kUpdateAvailable,
-                      metrics::CheckReaction::kUpdating,
-                      metrics::DownloadErrorCode::kUnset,
-                      &response,
-                      nullptr));
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kUpdateAvailable,
+                              metrics::CheckReaction::kUpdating,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
 
   EXPECT_EQ("testthe<url", response.more_info_url);
   EXPECT_EQ("testthe&codebase/file.signed",
@@ -1535,17 +1786,15 @@
   OmahaResponse response;
   // overflows int32_t:
   fake_update_response_.size = 123123123123123ull;
-  ASSERT_TRUE(
-      TestUpdateCheck(nullptr,  // request_params
-                      fake_update_response_.GetUpdateResponse(),
-                      -1,
-                      false,  // ping_only
-                      ErrorCode::kSuccess,
-                      metrics::CheckResult::kUpdateAvailable,
-                      metrics::CheckReaction::kUpdating,
-                      metrics::DownloadErrorCode::kUnset,
-                      &response,
-                      nullptr));
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kUpdateAvailable,
+                              metrics::CheckReaction::kUpdating,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
 
   EXPECT_EQ(fake_update_response_.size, response.packages[0].size);
 }
@@ -1560,8 +1809,7 @@
   // An existing but empty previous version means that we didn't reboot to a new
   // update, therefore, no need to update the previous version.
   EXPECT_CALL(prefs, SetString(kPrefsPreviousVersion, _)).Times(0);
-  ASSERT_FALSE(TestUpdateCheck(nullptr,  // request_params
-                               "invalid xml>",
+  ASSERT_FALSE(TestUpdateCheck("invalid xml>",
                                -1,
                                false,  // ping_only
                                ErrorCode::kOmahaRequestXMLParseError,
@@ -1572,9 +1820,9 @@
                                &post_data));
   // convert post_data to string
   string post_str(post_data.begin(), post_data.end());
-  EXPECT_NE(post_str.find(
-      "        <ping active=\"1\" a=\"-1\" r=\"-1\"></ping>\n"
-      "        <updatecheck targetversionprefix=\"\"></updatecheck>\n"),
+  EXPECT_NE(
+      post_str.find("        <ping active=\"1\" a=\"-1\" r=\"-1\"></ping>\n"
+                    "        <updatecheck></updatecheck>\n"),
       string::npos);
   EXPECT_NE(post_str.find("hardware_class=\"OEM MODEL 09235 7471\""),
             string::npos);
@@ -1589,8 +1837,7 @@
 
 TEST_F(OmahaRequestActionTest, FormatSuccessEventOutputTest) {
   brillo::Blob post_data;
-  TestEvent(request_params_,
-            new OmahaEvent(OmahaEvent::kTypeUpdateDownloadStarted),
+  TestEvent(new OmahaEvent(OmahaEvent::kTypeUpdateDownloadStarted),
             "invalid xml>",
             &post_data);
   // convert post_data to string
@@ -1606,8 +1853,7 @@
 
 TEST_F(OmahaRequestActionTest, FormatErrorEventOutputTest) {
   brillo::Blob post_data;
-  TestEvent(request_params_,
-            new OmahaEvent(OmahaEvent::kTypeDownloadComplete,
+  TestEvent(new OmahaEvent(OmahaEvent::kTypeDownloadComplete,
                            OmahaEvent::kResultError,
                            ErrorCode::kError),
             "invalid xml>",
@@ -1626,9 +1872,6 @@
 
 TEST_F(OmahaRequestActionTest, IsEventTest) {
   string http_response("doesn't matter");
-  // Create a copy of the OmahaRequestParams to reuse it later.
-  OmahaRequestParams params = request_params_;
-  fake_system_state_.set_request_params(&params);
   OmahaRequestAction update_check_action(
       &fake_system_state_,
       nullptr,
@@ -1638,8 +1881,6 @@
       false);
   EXPECT_FALSE(update_check_action.IsEvent());
 
-  params = request_params_;
-  fake_system_state_.set_request_params(&params);
   OmahaRequestAction event_action(
       &fake_system_state_,
       new OmahaEvent(OmahaEvent::kTypeUpdateComplete),
@@ -1655,24 +1896,10 @@
     bool delta_okay = i == 1;
     const char* delta_okay_str = delta_okay ? "true" : "false";
     brillo::Blob post_data;
-    OmahaRequestParams params(&fake_system_state_,
-                              constants::kOmahaPlatformName,
-                              OmahaRequestParams::kOsVersion,
-                              "service_pack",
-                              "x86-generic",
-                              kTestAppId,
-                              "0.1.0.0",
-                              "en-US",
-                              "unittest_track",
-                              "OEM MODEL REV 1234",
-                              "ChromeOSFirmware.1.0",
-                              "EC100",
-                              delta_okay,
-                              false,  // interactive
-                              "http://url",
-                              "");    // target_version_prefix
-    ASSERT_FALSE(TestUpdateCheck(&params,
-                                 "invalid xml>",
+
+    request_params_.set_delta_okay(delta_okay);
+
+    ASSERT_FALSE(TestUpdateCheck("invalid xml>",
                                  -1,
                                  false,  // ping_only
                                  ErrorCode::kOmahaRequestXMLParseError,
@@ -1696,24 +1923,10 @@
     const char* interactive_str = interactive ? "ondemandupdate" : "scheduler";
     brillo::Blob post_data;
     FakeSystemState fake_system_state;
-    OmahaRequestParams params(&fake_system_state_,
-                              constants::kOmahaPlatformName,
-                              OmahaRequestParams::kOsVersion,
-                              "service_pack",
-                              "x86-generic",
-                              kTestAppId,
-                              "0.1.0.0",
-                              "en-US",
-                              "unittest_track",
-                              "OEM MODEL REV 1234",
-                              "ChromeOSFirmware.1.0",
-                              "EC100",
-                              true,   // delta_okay
-                              interactive,
-                              "http://url",
-                              "");    // target_version_prefix
-    ASSERT_FALSE(TestUpdateCheck(&params,
-                                 "invalid xml>",
+
+    request_params_.set_interactive(interactive);
+
+    ASSERT_FALSE(TestUpdateCheck("invalid xml>",
                                  -1,
                                  false,  // ping_only
                                  ErrorCode::kOmahaRequestXMLParseError,
@@ -1724,13 +1937,75 @@
                                  &post_data));
     // convert post_data to string
     string post_str(post_data.begin(), post_data.end());
-    EXPECT_NE(post_str.find(base::StringPrintf("installsource=\"%s\"",
-                                               interactive_str)),
+    EXPECT_NE(post_str.find(
+                  base::StringPrintf("installsource=\"%s\"", interactive_str)),
               string::npos)
         << "i = " << i;
   }
 }
 
+TEST_F(OmahaRequestActionTest, FormatTargetVersionPrefixOutputTest) {
+  for (int i = 0; i < 2; i++) {
+    bool target_version_set = i == 1;
+    const char* target_version_prefix = target_version_set ? "10032." : "";
+    brillo::Blob post_data;
+    FakeSystemState fake_system_state;
+
+    request_params_.set_target_version_prefix(target_version_prefix);
+
+    ASSERT_FALSE(TestUpdateCheck("invalid xml>",
+                                 -1,
+                                 false,  // ping_only
+                                 ErrorCode::kOmahaRequestXMLParseError,
+                                 metrics::CheckResult::kParsingError,
+                                 metrics::CheckReaction::kUnset,
+                                 metrics::DownloadErrorCode::kUnset,
+                                 nullptr,
+                                 &post_data));
+    // convert post_data to string
+    string post_str(post_data.begin(), post_data.end());
+    if (target_version_set) {
+      EXPECT_NE(post_str.find("<updatecheck targetversionprefix=\"10032.\">"),
+                string::npos)
+          << "i = " << i;
+    } else {
+      EXPECT_EQ(post_str.find("targetversionprefix"), string::npos)
+          << "i = " << i;
+    }
+  }
+}
+
+TEST_F(OmahaRequestActionTest, FormatRollbackAllowedOutputTest) {
+  for (int i = 0; i < 4; i++) {
+    bool rollback_allowed = i / 2 == 0;
+    bool target_version_set = i % 2 == 0;
+    brillo::Blob post_data;
+    FakeSystemState fake_system_state;
+
+    request_params_.set_target_version_prefix(target_version_set ? "10032."
+                                                                 : "");
+    request_params_.set_rollback_allowed(rollback_allowed);
+
+    ASSERT_FALSE(TestUpdateCheck("invalid xml>",
+                                 -1,
+                                 false,  // ping_only
+                                 ErrorCode::kOmahaRequestXMLParseError,
+                                 metrics::CheckResult::kParsingError,
+                                 metrics::CheckReaction::kUnset,
+                                 metrics::DownloadErrorCode::kUnset,
+                                 nullptr,
+                                 &post_data));
+    // convert post_data to string
+    string post_str(post_data.begin(), post_data.end());
+    if (rollback_allowed && target_version_set) {
+      EXPECT_NE(post_str.find("rollback_allowed=\"true\""), string::npos)
+          << "i = " << i;
+    } else {
+      EXPECT_EQ(post_str.find("rollback_allowed"), string::npos) << "i = " << i;
+    }
+  }
+}
+
 TEST_F(OmahaRequestActionTest, OmahaEventTest) {
   OmahaEvent default_event;
   EXPECT_EQ(OmahaEvent::kTypeUnknown, default_event.type);
@@ -1768,8 +2043,7 @@
   EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
       .WillOnce(DoAll(SetArgPointee<1>(five_days_ago), Return(true)));
   brillo::Blob post_data;
-  ASSERT_TRUE(TestUpdateCheck(nullptr,  // request_params
-                              fake_update_response_.GetNoUpdateResponse(),
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetNoUpdateResponse(),
                               -1,
                               ping_only,
                               ErrorCode::kSuccess,
@@ -1814,17 +2088,15 @@
   EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
       .WillOnce(DoAll(SetArgPointee<1>(now), Return(true)));
   brillo::Blob post_data;
-  ASSERT_TRUE(
-      TestUpdateCheck(nullptr,  // request_params
-                      fake_update_response_.GetNoUpdateResponse(),
-                      -1,
-                      false,  // ping_only
-                      ErrorCode::kSuccess,
-                      metrics::CheckResult::kNoUpdateAvailable,
-                      metrics::CheckReaction::kUnset,
-                      metrics::DownloadErrorCode::kUnset,
-                      nullptr,
-                      &post_data));
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetNoUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kNoUpdateAvailable,
+                              metrics::CheckReaction::kUnset,
+                              metrics::DownloadErrorCode::kUnset,
+                              nullptr,
+                              &post_data));
   string post_str(post_data.begin(), post_data.end());
   EXPECT_NE(post_str.find("<ping active=\"1\" a=\"3\"></ping>"),
             string::npos);
@@ -1846,17 +2118,15 @@
   EXPECT_CALL(prefs, GetInt64(kPrefsLastRollCallPingDay, _))
       .WillOnce(DoAll(SetArgPointee<1>(four_days_ago), Return(true)));
   brillo::Blob post_data;
-  ASSERT_TRUE(
-      TestUpdateCheck(nullptr,  // request_params
-                      fake_update_response_.GetNoUpdateResponse(),
-                      -1,
-                      false,  // ping_only
-                      ErrorCode::kSuccess,
-                      metrics::CheckResult::kNoUpdateAvailable,
-                      metrics::CheckReaction::kUnset,
-                      metrics::DownloadErrorCode::kUnset,
-                      nullptr,
-                      &post_data));
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetNoUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kNoUpdateAvailable,
+                              metrics::CheckReaction::kUnset,
+                              metrics::DownloadErrorCode::kUnset,
+                              nullptr,
+                              &post_data));
   string post_str(post_data.begin(), post_data.end());
   EXPECT_NE(post_str.find("<ping active=\"1\" r=\"4\"></ping>\n"),
             string::npos);
@@ -1883,17 +2153,15 @@
   EXPECT_CALL(prefs, SetInt64(kPrefsLastRollCallPingDay, _))
       .WillOnce(Return(true));
   brillo::Blob post_data;
-  ASSERT_TRUE(
-      TestUpdateCheck(nullptr,  // request_params
-                      fake_update_response_.GetNoUpdateResponse(),
-                      -1,
-                      false,  // ping_only
-                      ErrorCode::kSuccess,
-                      metrics::CheckResult::kNoUpdateAvailable,
-                      metrics::CheckReaction::kUnset,
-                      metrics::DownloadErrorCode::kUnset,
-                      nullptr,
-                      &post_data));
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetNoUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kNoUpdateAvailable,
+                              metrics::CheckReaction::kUnset,
+                              metrics::DownloadErrorCode::kUnset,
+                              nullptr,
+                              &post_data));
   string post_str(post_data.begin(), post_data.end());
   EXPECT_EQ(post_str.find("ping"), string::npos);
 }
@@ -1910,17 +2178,15 @@
   EXPECT_CALL(prefs, SetInt64(kPrefsLastActivePingDay, _)).Times(0);
   EXPECT_CALL(prefs, SetInt64(kPrefsLastRollCallPingDay, _)).Times(0);
   brillo::Blob post_data;
-  EXPECT_TRUE(
-      TestUpdateCheck(nullptr,  // request_params
-                      fake_update_response_.GetNoUpdateResponse(),
-                      -1,
-                      true,  // ping_only
-                      ErrorCode::kSuccess,
-                      metrics::CheckResult::kUnset,
-                      metrics::CheckReaction::kUnset,
-                      metrics::DownloadErrorCode::kUnset,
-                      nullptr,
-                      &post_data));
+  EXPECT_TRUE(TestUpdateCheck(fake_update_response_.GetNoUpdateResponse(),
+                              -1,
+                              true,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kUnset,
+                              metrics::CheckReaction::kUnset,
+                              metrics::DownloadErrorCode::kUnset,
+                              nullptr,
+                              &post_data));
   EXPECT_EQ(0U, post_data.size());
 }
 
@@ -1944,8 +2210,7 @@
       .WillOnce(Return(true));
   brillo::Blob post_data;
   ASSERT_TRUE(
-      TestUpdateCheck(nullptr,  // request_params
-                      "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response "
+      TestUpdateCheck("<?xml version=\"1.0\" encoding=\"UTF-8\"?><response "
                       "protocol=\"3.0\"><daystart elapsed_seconds=\"100\"/>"
                       "<app appid=\"foo\" status=\"ok\"><ping status=\"ok\"/>"
                       "<updatecheck status=\"noupdate\"/></app></response>",
@@ -1981,8 +2246,7 @@
                               AllOf(Ge(midnight), Le(midnight_slack))))
       .WillOnce(Return(true));
   ASSERT_TRUE(
-      TestUpdateCheck(nullptr,  // request_params
-                      "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response "
+      TestUpdateCheck("<?xml version=\"1.0\" encoding=\"UTF-8\"?><response "
                       "protocol=\"3.0\"><daystart elapsed_seconds=\"200\"/>"
                       "<app appid=\"foo\" status=\"ok\"><ping status=\"ok\"/>"
                       "<updatecheck status=\"noupdate\"/></app></response>",
@@ -2004,8 +2268,7 @@
   EXPECT_CALL(prefs, SetInt64(kPrefsLastActivePingDay, _)).Times(0);
   EXPECT_CALL(prefs, SetInt64(kPrefsLastRollCallPingDay, _)).Times(0);
   ASSERT_TRUE(
-      TestUpdateCheck(nullptr,  // request_params
-                      "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response "
+      TestUpdateCheck("<?xml version=\"1.0\" encoding=\"UTF-8\"?><response "
                       "protocol=\"3.0\"><daystart blah=\"200\"/>"
                       "<app appid=\"foo\" status=\"ok\"><ping status=\"ok\"/>"
                       "<updatecheck status=\"noupdate\"/></app></response>",
@@ -2027,8 +2290,7 @@
   EXPECT_CALL(prefs, SetInt64(kPrefsLastActivePingDay, _)).Times(0);
   EXPECT_CALL(prefs, SetInt64(kPrefsLastRollCallPingDay, _)).Times(0);
   ASSERT_TRUE(
-      TestUpdateCheck(nullptr,  // request_params
-                      "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response "
+      TestUpdateCheck("<?xml version=\"1.0\" encoding=\"UTF-8\"?><response "
                       "protocol=\"3.0\"><daystart elapsed_seconds=\"x\"/>"
                       "<app appid=\"foo\" status=\"ok\"><ping status=\"ok\"/>"
                       "<updatecheck status=\"noupdate\"/></app></response>",
@@ -2046,8 +2308,7 @@
   // Test that the "eol" flags is only parsed from the "_eol" attribute and not
   // the "eol" attribute.
   ASSERT_TRUE(
-      TestUpdateCheck(nullptr,  // request_params
-                      "<?xml version=\"1.0\" encoding=\"UTF-8\"?><response "
+      TestUpdateCheck("<?xml version=\"1.0\" encoding=\"UTF-8\"?><response "
                       "protocol=\"3.0\"><app appid=\"foo\" status=\"ok\">"
                       "<ping status=\"ok\"/><updatecheck status=\"noupdate\" "
                       "_eol=\"security-only\" eol=\"eol\" _foo=\"bar\"/>"
@@ -2070,8 +2331,7 @@
 
 TEST_F(OmahaRequestActionTest, NoUniqueIDTest) {
   brillo::Blob post_data;
-  ASSERT_FALSE(TestUpdateCheck(nullptr,  // request_params
-                               "invalid xml>",
+  ASSERT_FALSE(TestUpdateCheck("invalid xml>",
                                -1,
                                false,  // ping_only
                                ErrorCode::kOmahaRequestXMLParseError,
@@ -2090,17 +2350,15 @@
   OmahaResponse response;
   const int http_error_code =
       static_cast<int>(ErrorCode::kOmahaRequestHTTPResponseBase) + 501;
-  ASSERT_FALSE(
-      TestUpdateCheck(nullptr,  // request_params
-                      "",
-                      501,
-                      false,  // ping_only
-                      static_cast<ErrorCode>(http_error_code),
-                      metrics::CheckResult::kDownloadError,
-                      metrics::CheckReaction::kUnset,
-                      static_cast<metrics::DownloadErrorCode>(501),
-                      &response,
-                      nullptr));
+  ASSERT_FALSE(TestUpdateCheck("",
+                               501,
+                               false,  // ping_only
+                               static_cast<ErrorCode>(http_error_code),
+                               metrics::CheckResult::kDownloadError,
+                               metrics::CheckReaction::kUnset,
+                               static_cast<metrics::DownloadErrorCode>(501),
+                               &response,
+                               nullptr));
   EXPECT_FALSE(response.update_exists);
 }
 
@@ -2108,32 +2366,28 @@
   OmahaResponse response;
   const int http_error_code =
       static_cast<int>(ErrorCode::kOmahaRequestHTTPResponseBase) + 999;
-  ASSERT_FALSE(
-      TestUpdateCheck(nullptr,  // request_params
-                      "",
-                      1500,
-                      false,  // ping_only
-                      static_cast<ErrorCode>(http_error_code),
-                      metrics::CheckResult::kDownloadError,
-                      metrics::CheckReaction::kUnset,
-                      metrics::DownloadErrorCode::kHttpStatusOther,
-                      &response,
-                      nullptr));
+  ASSERT_FALSE(TestUpdateCheck("",
+                               1500,
+                               false,  // ping_only
+                               static_cast<ErrorCode>(http_error_code),
+                               metrics::CheckResult::kDownloadError,
+                               metrics::CheckReaction::kUnset,
+                               metrics::DownloadErrorCode::kHttpStatusOther,
+                               &response,
+                               nullptr));
   EXPECT_FALSE(response.update_exists);
 }
 
 TEST_F(OmahaRequestActionTest, TestUpdateFirstSeenAtGetsPersistedFirstTime) {
   OmahaResponse response;
-  OmahaRequestParams params = request_params_;
-  params.set_wall_clock_based_wait_enabled(true);
-  params.set_waiting_period(TimeDelta().FromDays(1));
-  params.set_update_check_count_wait_enabled(false);
+  request_params_.set_wall_clock_based_wait_enabled(true);
+  request_params_.set_waiting_period(TimeDelta().FromDays(1));
+  request_params_.set_update_check_count_wait_enabled(false);
 
   Time arbitrary_date;
   ASSERT_TRUE(Time::FromString("6/4/1989", &arbitrary_date));
   fake_system_state_.fake_clock()->SetWallclockTime(arbitrary_date);
-  ASSERT_FALSE(TestUpdateCheck(&params,
-                               fake_update_response_.GetUpdateResponse(),
+  ASSERT_FALSE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
                                -1,
                                false,  // ping_only
                                ErrorCode::kOmahaUpdateDeferredPerPolicy,
@@ -2149,9 +2403,8 @@
   EXPECT_FALSE(response.update_exists);
 
   // Verify if we are interactive check we don't defer.
-  params.set_interactive(true);
-  ASSERT_TRUE(TestUpdateCheck(&params,
-                              fake_update_response_.GetUpdateResponse(),
+  request_params_.set_interactive(true);
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
                               -1,
                               false,  // ping_only
                               ErrorCode::kSuccess,
@@ -2165,10 +2418,9 @@
 
 TEST_F(OmahaRequestActionTest, TestUpdateFirstSeenAtGetsUsedIfAlreadyPresent) {
   OmahaResponse response;
-  OmahaRequestParams params = request_params_;
-  params.set_wall_clock_based_wait_enabled(true);
-  params.set_waiting_period(TimeDelta().FromDays(1));
-  params.set_update_check_count_wait_enabled(false);
+  request_params_.set_wall_clock_based_wait_enabled(true);
+  request_params_.set_waiting_period(TimeDelta().FromDays(1));
+  request_params_.set_update_check_count_wait_enabled(false);
 
   Time t1, t2;
   ASSERT_TRUE(Time::FromString("1/1/2012", &t1));
@@ -2176,8 +2428,7 @@
   ASSERT_TRUE(
       fake_prefs_.SetInt64(kPrefsUpdateFirstSeenAt, t1.ToInternalValue()));
   fake_system_state_.fake_clock()->SetWallclockTime(t2);
-  ASSERT_TRUE(TestUpdateCheck(&params,
-                              fake_update_response_.GetUpdateResponse(),
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
                               -1,
                               false,  // ping_only
                               ErrorCode::kSuccess,
@@ -2201,17 +2452,16 @@
   ASSERT_TRUE(tempdir.CreateUniqueTempDir());
 
   brillo::Blob post_data;
-  OmahaRequestParams params(&fake_system_state_);
-  params.set_root(tempdir.GetPath().value());
-  params.set_app_id("{22222222-2222-2222-2222-222222222222}");
-  params.set_app_version("1.2.3.4");
-  params.set_product_components("o.bundle=1");
-  params.set_current_channel("canary-channel");
-  EXPECT_TRUE(params.SetTargetChannel("stable-channel", true, nullptr));
-  params.UpdateDownloadChannel();
-  EXPECT_TRUE(params.ShouldPowerwash());
-  ASSERT_FALSE(TestUpdateCheck(&params,
-                               "invalid xml>",
+  request_params_.set_root(tempdir.GetPath().value());
+  request_params_.set_app_id("{22222222-2222-2222-2222-222222222222}");
+  request_params_.set_app_version("1.2.3.4");
+  request_params_.set_product_components("o.bundle=1");
+  request_params_.set_current_channel("canary-channel");
+  EXPECT_TRUE(
+      request_params_.SetTargetChannel("stable-channel", true, nullptr));
+  request_params_.UpdateDownloadChannel();
+  EXPECT_TRUE(request_params_.ShouldPowerwash());
+  ASSERT_FALSE(TestUpdateCheck("invalid xml>",
                                -1,
                                false,  // ping_only
                                ErrorCode::kOmahaRequestXMLParseError,
@@ -2235,17 +2485,16 @@
   ASSERT_TRUE(tempdir.CreateUniqueTempDir());
 
   brillo::Blob post_data;
-  OmahaRequestParams params(&fake_system_state_);
-  params.set_root(tempdir.GetPath().value());
-  params.set_app_id("{11111111-1111-1111-1111-111111111111}");
-  params.set_app_version("5.6.7.8");
-  params.set_product_components("o.bundle=1");
-  params.set_current_channel("stable-channel");
-  EXPECT_TRUE(params.SetTargetChannel("canary-channel", false, nullptr));
-  params.UpdateDownloadChannel();
-  EXPECT_FALSE(params.ShouldPowerwash());
-  ASSERT_FALSE(TestUpdateCheck(&params,
-                               "invalid xml>",
+  request_params_.set_root(tempdir.GetPath().value());
+  request_params_.set_app_id("{11111111-1111-1111-1111-111111111111}");
+  request_params_.set_app_version("5.6.7.8");
+  request_params_.set_product_components("o.bundle=1");
+  request_params_.set_current_channel("stable-channel");
+  EXPECT_TRUE(
+      request_params_.SetTargetChannel("canary-channel", false, nullptr));
+  request_params_.UpdateDownloadChannel();
+  EXPECT_FALSE(request_params_.ShouldPowerwash());
+  ASSERT_FALSE(TestUpdateCheck("invalid xml>",
                                -1,
                                false,  // ping_only
                                ErrorCode::kOmahaRequestXMLParseError,
@@ -2273,17 +2522,15 @@
   fake_system_state_.fake_hardware()->SetPowerwashCount(1);
 
   brillo::Blob post_data;
-  ASSERT_TRUE(
-      TestUpdateCheck(nullptr,  // request_params
-                      fake_update_response_.GetNoUpdateResponse(),
-                      -1,
-                      false,  // ping_only
-                      ErrorCode::kSuccess,
-                      metrics::CheckResult::kNoUpdateAvailable,
-                      metrics::CheckReaction::kUnset,
-                      metrics::DownloadErrorCode::kUnset,
-                      nullptr,
-                      &post_data));
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetNoUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kNoUpdateAvailable,
+                              metrics::CheckReaction::kUnset,
+                              metrics::DownloadErrorCode::kUnset,
+                              nullptr,
+                              &post_data));
   // We shouldn't send a ping in this case since powerwash > 0.
   string post_str(post_data.begin(), post_data.end());
   EXPECT_EQ(string::npos, post_str.find("<ping"));
@@ -2301,17 +2548,15 @@
   fake_system_state_.fake_hardware()->SetFirstActiveOmahaPingSent();
 
   brillo::Blob post_data;
-  ASSERT_TRUE(
-      TestUpdateCheck(nullptr,  // request_params
-                      fake_update_response_.GetNoUpdateResponse(),
-                      -1,
-                      false,  // ping_only
-                      ErrorCode::kSuccess,
-                      metrics::CheckResult::kNoUpdateAvailable,
-                      metrics::CheckReaction::kUnset,
-                      metrics::DownloadErrorCode::kUnset,
-                      nullptr,
-                      &post_data));
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetNoUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kNoUpdateAvailable,
+                              metrics::CheckReaction::kUnset,
+                              metrics::DownloadErrorCode::kUnset,
+                              nullptr,
+                              &post_data));
   // We shouldn't send a ping in this case since
   // first_active_omaha_ping_sent=true
   string post_str(post_data.begin(), post_data.end());
@@ -2324,17 +2569,15 @@
   fake_prefs_.SetString(kPrefsPreviousVersion, "1.2.3.4");
 
   brillo::Blob post_data;
-  ASSERT_TRUE(
-      TestUpdateCheck(nullptr,  // request_params
-                      fake_update_response_.GetNoUpdateResponse(),
-                      -1,
-                      false,  // ping_only
-                      ErrorCode::kSuccess,
-                      metrics::CheckResult::kNoUpdateAvailable,
-                      metrics::CheckReaction::kUnset,
-                      metrics::DownloadErrorCode::kUnset,
-                      nullptr,
-                      &post_data));
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetNoUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kNoUpdateAvailable,
+                              metrics::CheckReaction::kUnset,
+                              metrics::DownloadErrorCode::kUnset,
+                              nullptr,
+                              &post_data));
   string post_str(post_data.begin(), post_data.end());
 
   // An event 54 is included and has the right version.
@@ -2364,7 +2607,6 @@
     bool expected_allow_p2p_for_sharing,
     const string& expected_p2p_url) {
   OmahaResponse response;
-  OmahaRequestParams request_params = request_params_;
   bool actual_allow_p2p_for_downloading = initial_allow_p2p_for_downloading;
   bool actual_allow_p2p_for_sharing = initial_allow_p2p_for_sharing;
   string actual_p2p_url;
@@ -2395,17 +2637,15 @@
   fake_update_response_.disable_p2p_for_downloading =
       omaha_disable_p2p_for_downloading;
   fake_update_response_.disable_p2p_for_sharing = omaha_disable_p2p_for_sharing;
-  ASSERT_TRUE(
-      TestUpdateCheck(&request_params,
-                      fake_update_response_.GetUpdateResponse(),
-                      -1,
-                      false,  // ping_only
-                      ErrorCode::kSuccess,
-                      metrics::CheckResult::kUpdateAvailable,
-                      metrics::CheckReaction::kUpdating,
-                      metrics::DownloadErrorCode::kUnset,
-                      &response,
-                      nullptr));
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kUpdateAvailable,
+                              metrics::CheckReaction::kUpdating,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
   EXPECT_TRUE(response.update_exists);
 
   EXPECT_EQ(omaha_disable_p2p_for_downloading,
@@ -2500,17 +2740,15 @@
 bool OmahaRequestActionTest::InstallDateParseHelper(const string &elapsed_days,
                                                     OmahaResponse *response) {
   fake_update_response_.elapsed_days = elapsed_days;
-  return
-      TestUpdateCheck(nullptr,  // request_params
-                      fake_update_response_.GetUpdateResponse(),
-                      -1,
-                      false,  // ping_only
-                      ErrorCode::kSuccess,
-                      metrics::CheckResult::kUpdateAvailable,
-                      metrics::CheckReaction::kUpdating,
-                      metrics::DownloadErrorCode::kUnset,
-                      response,
-                      nullptr);
+  return TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
+                         -1,
+                         false,  // ping_only
+                         ErrorCode::kSuccess,
+                         metrics::CheckResult::kUpdateAvailable,
+                         metrics::CheckReaction::kUpdating,
+                         metrics::DownloadErrorCode::kUnset,
+                         response,
+                         nullptr);
 }
 
 TEST_F(OmahaRequestActionTest, ParseInstallDateFromResponse) {
@@ -2612,4 +2850,162 @@
   EXPECT_EQ(prefs_days, 28);
 }
 
+// Verifies that a device with no device policy, and is not a consumer
+// device sets the max kernel key version to the current version.
+// ie. the same behavior as if rollback is enabled.
+TEST_F(OmahaRequestActionTest, NoPolicyEnterpriseDevicesSetMaxRollback) {
+  FakeHardware* fake_hw = fake_system_state_.fake_hardware();
+
+  // Setup and verify some initial default values for the kernel TPM
+  // values that control verified boot and rollback.
+  const int min_kernel_version = 4;
+  fake_hw->SetMinKernelKeyVersion(min_kernel_version);
+  fake_hw->SetMaxKernelKeyRollforward(kRollforwardInfinity);
+  EXPECT_EQ(min_kernel_version, fake_hw->GetMinKernelKeyVersion());
+  EXPECT_EQ(kRollforwardInfinity, fake_hw->GetMaxKernelKeyRollforward());
+
+  EXPECT_CALL(
+      *fake_system_state_.mock_metrics_reporter(),
+      ReportKeyVersionMetrics(min_kernel_version, min_kernel_version, true))
+      .Times(1);
+
+  OmahaResponse response;
+  TestRollbackCheck(false /* is_consumer_device */,
+                    3 /* rollback_allowed_milestones */,
+                    false /* is_policy_loaded */,
+                    &response);
+
+  // Verify kernel_max_rollforward was set to the current minimum
+  // kernel key version. This has the effect of freezing roll
+  // forwards indefinitely. This will hold the rollback window
+  // open until a future change will be able to move this forward
+  // relative the configured window.
+  EXPECT_EQ(min_kernel_version, fake_hw->GetMinKernelKeyVersion());
+  EXPECT_EQ(min_kernel_version, fake_hw->GetMaxKernelKeyRollforward());
+}
+
+// Verifies that a conmsumer device with no device policy sets the
+// max kernel key version to the current version. ie. the same
+// behavior as if rollback is enabled.
+TEST_F(OmahaRequestActionTest, NoPolicyConsumerDevicesSetMaxRollback) {
+  FakeHardware* fake_hw = fake_system_state_.fake_hardware();
+
+  // Setup and verify some initial default values for the kernel TPM
+  // values that control verified boot and rollback.
+  const int min_kernel_version = 3;
+  fake_hw->SetMinKernelKeyVersion(min_kernel_version);
+  fake_hw->SetMaxKernelKeyRollforward(kRollforwardInfinity);
+  EXPECT_EQ(min_kernel_version, fake_hw->GetMinKernelKeyVersion());
+  EXPECT_EQ(kRollforwardInfinity, fake_hw->GetMaxKernelKeyRollforward());
+
+  EXPECT_CALL(
+      *fake_system_state_.mock_metrics_reporter(),
+      ReportKeyVersionMetrics(min_kernel_version, kRollforwardInfinity, true))
+      .Times(1);
+
+  OmahaResponse response;
+  TestRollbackCheck(true /* is_consumer_device */,
+                    3 /* rollback_allowed_milestones */,
+                    false /* is_policy_loaded */,
+                    &response);
+
+  // Verify that with rollback disabled that kernel_max_rollforward
+  // was set to logical infinity. This is the expected behavior for
+  // consumer devices and matches the existing behavior prior to the
+  // rollback features.
+  EXPECT_EQ(min_kernel_version, fake_hw->GetMinKernelKeyVersion());
+  EXPECT_EQ(kRollforwardInfinity, fake_hw->GetMaxKernelKeyRollforward());
+}
+
+// Verifies that a device with rollback enabled sets kernel_max_rollforward
+// in the TPM to prevent roll forward.
+TEST_F(OmahaRequestActionTest, RollbackEnabledDevicesSetMaxRollback) {
+  FakeHardware* fake_hw = fake_system_state_.fake_hardware();
+
+  // Setup and verify some initial default values for the kernel TPM
+  // values that control verified boot and rollback.
+  const int allowed_milestones = 4;
+  const int min_kernel_version = 3;
+  fake_hw->SetMinKernelKeyVersion(min_kernel_version);
+  fake_hw->SetMaxKernelKeyRollforward(kRollforwardInfinity);
+  EXPECT_EQ(min_kernel_version, fake_hw->GetMinKernelKeyVersion());
+  EXPECT_EQ(kRollforwardInfinity, fake_hw->GetMaxKernelKeyRollforward());
+
+  EXPECT_CALL(
+      *fake_system_state_.mock_metrics_reporter(),
+      ReportKeyVersionMetrics(min_kernel_version, min_kernel_version, true))
+      .Times(1);
+
+  OmahaResponse response;
+  TestRollbackCheck(false /* is_consumer_device */,
+                    allowed_milestones,
+                    true /* is_policy_loaded */,
+                    &response);
+
+  // Verify that with rollback enabled that kernel_max_rollforward
+  // was set to the current minimum kernel key version. This has
+  // the effect of freezing roll forwards indefinitely. This will
+  // hold the rollback window open until a future change will
+  // be able to move this forward relative the configured window.
+  EXPECT_EQ(min_kernel_version, fake_hw->GetMinKernelKeyVersion());
+  EXPECT_EQ(min_kernel_version, fake_hw->GetMaxKernelKeyRollforward());
+}
+
+// Verifies that a device with rollback disabled sets kernel_max_rollforward
+// in the TPM to logical infinity, to allow roll forward.
+TEST_F(OmahaRequestActionTest, RollbackDisabledDevicesSetMaxRollback) {
+  FakeHardware* fake_hw = fake_system_state_.fake_hardware();
+
+  // Setup and verify some initial default values for the kernel TPM
+  // values that control verified boot and rollback.
+  const int allowed_milestones = 0;
+  const int min_kernel_version = 3;
+  fake_hw->SetMinKernelKeyVersion(min_kernel_version);
+  fake_hw->SetMaxKernelKeyRollforward(kRollforwardInfinity);
+  EXPECT_EQ(min_kernel_version, fake_hw->GetMinKernelKeyVersion());
+  EXPECT_EQ(kRollforwardInfinity, fake_hw->GetMaxKernelKeyRollforward());
+
+  EXPECT_CALL(
+      *fake_system_state_.mock_metrics_reporter(),
+      ReportKeyVersionMetrics(min_kernel_version, kRollforwardInfinity, true))
+      .Times(1);
+
+  OmahaResponse response;
+  TestRollbackCheck(false /* is_consumer_device */,
+                    allowed_milestones,
+                    true /* is_policy_loaded */,
+                    &response);
+
+  // Verify that with rollback disabled that kernel_max_rollforward
+  // was set to logical infinity.
+  EXPECT_EQ(min_kernel_version, fake_hw->GetMinKernelKeyVersion());
+  EXPECT_EQ(kRollforwardInfinity, fake_hw->GetMaxKernelKeyRollforward());
+}
+
+TEST_F(OmahaRequestActionTest, RollbackResponseParsedNoEntries) {
+  OmahaResponse response;
+  fake_update_response_.rollback = true;
+  TestRollbackCheck(false /* is_consumer_device */,
+                    4 /* rollback_allowed_milestones */,
+                    true /* is_policy_loaded */,
+                    &response);
+  EXPECT_TRUE(response.is_rollback);
+}
+
+TEST_F(OmahaRequestActionTest, RollbackResponseValidVersionsParsed) {
+  OmahaResponse response;
+  fake_update_response_.rollback_firmware_version = "1.2";
+  fake_update_response_.rollback_kernel_version = "3.4";
+  fake_update_response_.rollback = true;
+  TestRollbackCheck(false /* is_consumer_device */,
+                    4 /* rollback_allowed_milestones */,
+                    true /* is_policy_loaded */,
+                    &response);
+  EXPECT_TRUE(response.is_rollback);
+  EXPECT_EQ(1, response.rollback_key_version.firmware_key);
+  EXPECT_EQ(2, response.rollback_key_version.firmware);
+  EXPECT_EQ(3, response.rollback_key_version.kernel_key);
+  EXPECT_EQ(4, response.rollback_key_version.kernel);
+}
+
 }  // namespace chromeos_update_engine
diff --git a/omaha_request_params.cc b/omaha_request_params.cc
index 97a2ed1..186ea61 100644
--- a/omaha_request_params.cc
+++ b/omaha_request_params.cc
@@ -131,16 +131,10 @@
 }
 
 bool OmahaRequestParams::CollectECFWVersions() const {
-  return base::StartsWith(hwid_, string("SAMS ALEX"),
-                          base::CompareCase::SENSITIVE) ||
-         base::StartsWith(hwid_, string("BUTTERFLY"),
-                          base::CompareCase::SENSITIVE) ||
-         base::StartsWith(hwid_, string("LUMPY"),
-                          base::CompareCase::SENSITIVE) ||
-         base::StartsWith(hwid_, string("PARROT"),
-                          base::CompareCase::SENSITIVE) ||
-         base::StartsWith(hwid_, string("SPRING"),
-                          base::CompareCase::SENSITIVE) ||
+  return base::StartsWith(
+             hwid_, string("PARROT"), base::CompareCase::SENSITIVE) ||
+         base::StartsWith(
+             hwid_, string("SPRING"), base::CompareCase::SENSITIVE) ||
          base::StartsWith(hwid_, string("SNOW"), base::CompareCase::SENSITIVE);
 }
 
diff --git a/omaha_request_params.h b/omaha_request_params.h
index 60619f9..c8e26b5 100644
--- a/omaha_request_params.h
+++ b/omaha_request_params.h
@@ -49,52 +49,12 @@
         os_version_(kOsVersion),
         delta_okay_(true),
         interactive_(false),
+        rollback_allowed_(false),
         wall_clock_based_wait_enabled_(false),
         update_check_count_wait_enabled_(false),
         min_update_checks_needed_(kDefaultMinUpdateChecks),
         max_update_checks_allowed_(kDefaultMaxUpdateChecks) {}
 
-  OmahaRequestParams(SystemState* system_state,
-                     const std::string& in_os_platform,
-                     const std::string& in_os_version,
-                     const std::string& in_os_sp,
-                     const std::string& in_os_board,
-                     const std::string& in_app_id,
-                     const std::string& in_app_version,
-                     const std::string& in_app_lang,
-                     const std::string& in_target_channel,
-                     const std::string& in_hwid,
-                     const std::string& in_fw_version,
-                     const std::string& in_ec_version,
-                     bool in_delta_okay,
-                     bool in_interactive,
-                     const std::string& in_update_url,
-                     const std::string& in_target_version_prefix)
-      : system_state_(system_state),
-        os_platform_(in_os_platform),
-        os_version_(in_os_version),
-        os_sp_(in_os_sp),
-        app_lang_(in_app_lang),
-        hwid_(in_hwid),
-        fw_version_(in_fw_version),
-        ec_version_(in_ec_version),
-        delta_okay_(in_delta_okay),
-        interactive_(in_interactive),
-        update_url_(in_update_url),
-        target_version_prefix_(in_target_version_prefix),
-        wall_clock_based_wait_enabled_(false),
-        update_check_count_wait_enabled_(false),
-        min_update_checks_needed_(kDefaultMinUpdateChecks),
-        max_update_checks_allowed_(kDefaultMaxUpdateChecks) {
-    image_props_.board = in_os_board;
-    image_props_.product_id = in_app_id;
-    image_props_.canary_product_id = in_app_id;
-    image_props_.version = in_app_version;
-    image_props_.current_channel = in_target_channel;
-    mutable_image_props_.target_channel = in_target_channel;
-    mutable_image_props_.is_powerwash_allowed = false;
-  }
-
   virtual ~OmahaRequestParams();
 
   // Setters and getters for the various properties.
@@ -164,6 +124,12 @@
     return target_version_prefix_;
   }
 
+  inline void set_rollback_allowed(bool rollback_allowed) {
+    rollback_allowed_ = rollback_allowed;
+  }
+
+  inline bool rollback_allowed() const { return rollback_allowed_; }
+
   inline void set_wall_clock_based_wait_enabled(bool enabled) {
     wall_clock_based_wait_enabled_ = enabled;
   }
@@ -204,7 +170,6 @@
 
   // Suggested defaults
   static const char kOsVersion[];
-  static const char kIsPowerwashAllowedKey[];
   static const int64_t kDefaultMinUpdateChecks = 0;
   static const int64_t kDefaultMaxUpdateChecks = 8;
 
@@ -249,6 +214,21 @@
   void set_target_channel(const std::string& channel) {
     mutable_image_props_.target_channel = channel;
   }
+  void set_os_sp(const std::string& os_sp) { os_sp_ = os_sp; }
+  void set_os_board(const std::string& os_board) {
+    image_props_.board = os_board;
+  }
+  void set_app_lang(const std::string& app_lang) { app_lang_ = app_lang; }
+  void set_hwid(const std::string& hwid) { hwid_ = hwid; }
+  void set_fw_version(const std::string& fw_version) {
+    fw_version_ = fw_version;
+  }
+  void set_ec_version(const std::string& ec_version) {
+    ec_version_ = ec_version;
+  }
+  void set_is_powerwash_allowed(bool powerwash_allowed) {
+    mutable_image_props_.is_powerwash_allowed = powerwash_allowed;
+  }
 
  private:
   FRIEND_TEST(OmahaRequestParamsTest, ChannelIndexTest);
@@ -279,15 +259,6 @@
   // Compares hwid to a set of whitelisted prefixes.
   bool CollectECFWVersions() const;
 
-  // These are individual helper methods to initialize the said properties from
-  // the LSB value.
-  void SetTargetChannelFromLsbValue();
-  void SetCurrentChannelFromLsbValue();
-  void SetIsPowerwashAllowedFromLsbValue();
-
-  // Initializes the required properties from the LSB value.
-  void InitFromLsbValue();
-
   // Gets the machine type (e.g. "i686").
   std::string GetMachineType() const;
 
@@ -337,14 +308,17 @@
   // to be pinned to. It's empty otherwise.
   std::string target_version_prefix_;
 
-  // True if scattering is enabled, in which case waiting_period_ specifies the
-  // amount of absolute time that we've to wait for before sending a request to
-  // Omaha.
+  // Whether the client is accepting rollback images too.
+  bool rollback_allowed_;
+
+  // True if scattering or staging are enabled, in which case waiting_period_
+  // specifies the amount of absolute time that we've to wait for before sending
+  // a request to Omaha.
   bool wall_clock_based_wait_enabled_;
   base::TimeDelta waiting_period_;
 
-  // True if scattering is enabled to denote the number of update checks
-  // we've to skip before we can send a request to Omaha. The min and max
+  // True if scattering or staging are enabled to denote the number of update
+  // checks we've to skip before we can send a request to Omaha. The min and max
   // values establish the bounds for a random number to be chosen within that
   // range to enable such a wait.
   bool update_check_count_wait_enabled_;
@@ -354,9 +328,7 @@
   // When reading files, prepend root_ to the paths. Useful for testing.
   std::string root_;
 
-  // TODO(jaysri): Uncomment this after fixing unit tests, as part of
-  // chromium-os:39752
-  // DISALLOW_COPY_AND_ASSIGN(OmahaRequestParams);
+  DISALLOW_COPY_AND_ASSIGN(OmahaRequestParams);
 };
 
 }  // namespace chromeos_update_engine
diff --git a/omaha_request_params_unittest.cc b/omaha_request_params_unittest.cc
index ce77f31..7332431 100644
--- a/omaha_request_params_unittest.cc
+++ b/omaha_request_params_unittest.cc
@@ -44,9 +44,6 @@
   void SetUp() override {
     // Create a uniquely named test directory.
     ASSERT_TRUE(tempdir_.CreateUniqueTempDir());
-    // Create a fresh copy of the params for each test, so there's no
-    // unintended reuse of state across tests.
-    params_ = OmahaRequestParams(&fake_system_state_);
     params_.set_root(tempdir_.GetPath().value());
     SetLockDown(false);
     fake_system_state_.set_prefs(&fake_prefs_);
@@ -57,8 +54,8 @@
     fake_system_state_.fake_hardware()->SetIsNormalBootMode(locked_down);
   }
 
-  OmahaRequestParams params_;
   FakeSystemState fake_system_state_;
+  OmahaRequestParams params_{&fake_system_state_};
   FakePrefs fake_prefs_;
 
   base::ScopedTempDir tempdir_;
@@ -259,9 +256,6 @@
 
   params_.hwid_ = string("SNOW 12345");
   EXPECT_TRUE(params_.CollectECFWVersions());
-
-  params_.hwid_ = string("SAMS ALEX 12345");
-  EXPECT_TRUE(params_.CollectECFWVersions());
 }
 
 }  // namespace chromeos_update_engine
diff --git a/omaha_response.h b/omaha_response.h
index 3fb2f6d..0ac09df 100644
--- a/omaha_response.h
+++ b/omaha_response.h
@@ -21,6 +21,7 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 
+#include <limits>
 #include <string>
 #include <vector>
 
@@ -83,6 +84,24 @@
   // PST, according to the Omaha Server's clock and timezone (PST8PDT,
   // aka "Pacific Time".)
   int install_date_days = -1;
+
+  // True if the returned image is a rollback for the device.
+  bool is_rollback = false;
+
+  struct RollbackKeyVersion {
+    // Kernel key version. 0xffff if the value is unknown.
+    uint16_t kernel_key = std::numeric_limits<uint16_t>::max();
+    // Kernel version. 0xffff if the value is unknown.
+    uint16_t kernel = std::numeric_limits<uint16_t>::max();
+    // Firmware key verison. 0xffff if the value is unknown.
+    uint16_t firmware_key = std::numeric_limits<uint16_t>::max();
+    // Firmware version. 0xffff if the value is unknown.
+    uint16_t firmware = std::numeric_limits<uint16_t>::max();
+  };
+
+  // Key versions of the returned rollback image. Values are 0xffff if the
+  // image not a rollback, or the fields were not present.
+  RollbackKeyVersion rollback_key_version;
 };
 static_assert(sizeof(off_t) == 8, "off_t not 64 bit");
 
diff --git a/omaha_response_handler_action.cc b/omaha_response_handler_action.cc
index 5a1d7b1..7e6da5d 100644
--- a/omaha_response_handler_action.cc
+++ b/omaha_response_handler_action.cc
@@ -16,6 +16,7 @@
 
 #include "update_engine/omaha_response_handler_action.h"
 
+#include <limits>
 #include <string>
 
 #include <base/logging.h>
@@ -36,20 +37,15 @@
 
 using chromeos_update_manager::Policy;
 using chromeos_update_manager::UpdateManager;
+using std::numeric_limits;
 using std::string;
 
 namespace chromeos_update_engine {
 
 OmahaResponseHandlerAction::OmahaResponseHandlerAction(
     SystemState* system_state)
-    : OmahaResponseHandlerAction(system_state,
-                                 constants::kOmahaResponseDeadlineFile) {}
-
-OmahaResponseHandlerAction::OmahaResponseHandlerAction(
-    SystemState* system_state, const string& deadline_file)
     : system_state_(system_state),
-      key_path_(constants::kUpdatePayloadPublicKeyPath),
-      deadline_file_(deadline_file) {}
+      deadline_file_(constants::kOmahaResponseDeadlineFile) {}
 
 void OmahaResponseHandlerAction::PerformAction() {
   CHECK(HasInputObject());
@@ -138,6 +134,38 @@
   system_state_->prefs()->SetString(current_channel_key,
                                     params->download_channel());
 
+  // Checking whether device is able to boot up the returned rollback image.
+  if (response.is_rollback) {
+    if (!params->rollback_allowed()) {
+      LOG(ERROR) << "Received rollback image but rollback is not allowed.";
+      completer.set_code(ErrorCode::kOmahaResponseInvalid);
+      return;
+    }
+    auto min_kernel_key_version = static_cast<uint32_t>(
+        system_state_->hardware()->GetMinKernelKeyVersion());
+    auto min_firmware_key_version = static_cast<uint32_t>(
+        system_state_->hardware()->GetMinFirmwareKeyVersion());
+    uint32_t kernel_key_version =
+        static_cast<uint32_t>(response.rollback_key_version.kernel_key) << 16 |
+        static_cast<uint32_t>(response.rollback_key_version.kernel);
+    uint32_t firmware_key_version =
+        static_cast<uint32_t>(response.rollback_key_version.firmware_key)
+            << 16 |
+        static_cast<uint32_t>(response.rollback_key_version.firmware);
+
+    // Don't attempt a rollback if the versions are incompatible or the
+    // target image does not specify the version information.
+    if (kernel_key_version == numeric_limits<uint32_t>::max() ||
+        firmware_key_version == numeric_limits<uint32_t>::max() ||
+        kernel_key_version < min_kernel_key_version ||
+        firmware_key_version < min_firmware_key_version) {
+      LOG(ERROR) << "Device won't be able to boot up the rollback image.";
+      completer.set_code(ErrorCode::kRollbackNotPossible);
+      return;
+    }
+    install_plan_.is_rollback = true;
+  }
+
   if (response.powerwash_required || params->ShouldPowerwash())
     install_plan_.powerwash_required = true;
 
@@ -155,9 +183,16 @@
   // method and UpdateStatus signal. A potential issue is that update_engine may
   // be unresponsive during an update download.
   if (!deadline_file_.empty()) {
-    utils::WriteFile(deadline_file_.c_str(),
-                     response.deadline.data(),
-                     response.deadline.size());
+    if (payload_state->GetRollbackHappened()) {
+      // Don't do forced update if rollback has happened since the last update
+      // check where policy was present.
+      LOG(INFO) << "Not forcing update because a rollback happened.";
+      utils::WriteFile(deadline_file_.c_str(), nullptr, 0);
+    } else {
+      utils::WriteFile(deadline_file_.c_str(),
+                       response.deadline.data(),
+                       response.deadline.size());
+    }
     chmod(deadline_file_.c_str(), S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
   }
 
diff --git a/omaha_response_handler_action.h b/omaha_response_handler_action.h
index 63c7b2d..344fc1d 100644
--- a/omaha_response_handler_action.h
+++ b/omaha_response_handler_action.h
@@ -59,7 +59,6 @@
   // Debugging/logging
   static std::string StaticType() { return "OmahaResponseHandlerAction"; }
   std::string Type() const override { return StaticType(); }
-  void set_key_path(const std::string& path) { key_path_ = path; }
 
  private:
   // Returns true if payload hash checks are mandatory based on the state
@@ -72,19 +71,18 @@
   // The install plan, if we have an update.
   InstallPlan install_plan_;
 
-  // Public key path to use for payload verification.
-  std::string key_path_;
-
   // File used for communication deadline to Chrome.
-  const std::string deadline_file_;
-
-  // Special ctor + friend declarations for testing purposes.
-  OmahaResponseHandlerAction(SystemState* system_state,
-                             const std::string& deadline_file);
+  std::string deadline_file_;
 
   friend class OmahaResponseHandlerActionTest;
-
+  friend class OmahaResponseHandlerActionProcessorDelegate;
   FRIEND_TEST(UpdateAttempterTest, CreatePendingErrorEventResumedTest);
+  FRIEND_TEST(UpdateAttempterTest, RollbackMetricsNotRollbackFailure);
+  FRIEND_TEST(UpdateAttempterTest, RollbackMetricsNotRollbackSuccess);
+  FRIEND_TEST(UpdateAttempterTest, RollbackMetricsRollbackFailure);
+  FRIEND_TEST(UpdateAttempterTest, RollbackMetricsRollbackSuccess);
+  FRIEND_TEST(UpdateAttempterTest, SetRollbackHappenedNotRollback);
+  FRIEND_TEST(UpdateAttempterTest, SetRollbackHappenedRollback);
   FRIEND_TEST(UpdateAttempterTest, UpdateDeferredByPolicyTest);
 
   DISALLOW_COPY_AND_ASSIGN(OmahaResponseHandlerAction);
diff --git a/omaha_response_handler_action_unittest.cc b/omaha_response_handler_action_unittest.cc
index 909452a..4e101ee 100644
--- a/omaha_response_handler_action_unittest.cc
+++ b/omaha_response_handler_action_unittest.cc
@@ -18,6 +18,7 @@
 
 #include <memory>
 #include <string>
+#include <utility>
 
 #include <base/files/file_util.h>
 #include <base/files/scoped_temp_dir.h>
@@ -46,38 +47,6 @@
 
 namespace chromeos_update_engine {
 
-class OmahaResponseHandlerActionTest : public ::testing::Test {
- protected:
-  void SetUp() override {
-    FakeBootControl* fake_boot_control = fake_system_state_.fake_boot_control();
-    fake_boot_control->SetPartitionDevice(
-        kLegacyPartitionNameKernel, 0, "/dev/sdz2");
-    fake_boot_control->SetPartitionDevice(
-        kLegacyPartitionNameRoot, 0, "/dev/sdz3");
-    fake_boot_control->SetPartitionDevice(
-        kLegacyPartitionNameKernel, 1, "/dev/sdz4");
-    fake_boot_control->SetPartitionDevice(
-        kLegacyPartitionNameRoot, 1, "/dev/sdz5");
-  }
-
-  // Return true iff the OmahaResponseHandlerAction succeeded.
-  // If out is non-null, it's set w/ the response from the action.
-  bool DoTest(const OmahaResponse& in,
-              const string& deadline_file,
-              InstallPlan* out);
-
-  // Pointer to the Action, valid after |DoTest|, released when the test is
-  // finished.
-  std::unique_ptr<OmahaResponseHandlerAction> action_;
-  // Captures the action's result code, for tests that need to directly verify
-  // it in non-success cases.
-  ErrorCode action_result_code_;
-
-  FakeSystemState fake_system_state_;
-  // "Hash+"
-  const brillo::Blob expected_hash_ = {0x48, 0x61, 0x73, 0x68, 0x2b};
-};
-
 class OmahaResponseHandlerActionProcessorDelegate
     : public ActionProcessorDelegate {
  public:
@@ -87,12 +56,56 @@
                        AbstractAction* action,
                        ErrorCode code) {
     if (action->Type() == OmahaResponseHandlerAction::StaticType()) {
+      auto response_handler_action =
+          static_cast<OmahaResponseHandlerAction*>(action);
       code_ = code;
       code_set_ = true;
+      response_handler_action_install_plan_.reset(
+          new InstallPlan(response_handler_action->install_plan_));
+    } else if (action->Type() ==
+               ObjectCollectorAction<InstallPlan>::StaticType()) {
+      auto collector_action =
+          static_cast<ObjectCollectorAction<InstallPlan>*>(action);
+      collector_action_install_plan_.reset(
+          new InstallPlan(collector_action->object()));
     }
   }
   ErrorCode code_;
   bool code_set_;
+  std::unique_ptr<InstallPlan> collector_action_install_plan_;
+  std::unique_ptr<InstallPlan> response_handler_action_install_plan_;
+};
+
+class OmahaResponseHandlerActionTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    FakeBootControl* fake_boot_control = fake_system_state_.fake_boot_control();
+    fake_boot_control->SetPartitionDevice(
+        kPartitionNameKernel, 0, "/dev/sdz2");
+    fake_boot_control->SetPartitionDevice(
+        kPartitionNameRoot, 0, "/dev/sdz3");
+    fake_boot_control->SetPartitionDevice(
+        kPartitionNameKernel, 1, "/dev/sdz4");
+    fake_boot_control->SetPartitionDevice(
+        kPartitionNameRoot, 1, "/dev/sdz5");
+  }
+
+  // Return true iff the OmahaResponseHandlerAction succeeded.
+  // If out is non-null, it's set w/ the response from the action.
+  bool DoTest(const OmahaResponse& in,
+              const string& deadline_file,
+              InstallPlan* out);
+
+  // Delegate passed to the ActionProcessor.
+  OmahaResponseHandlerActionProcessorDelegate delegate_;
+
+  // Captures the action's result code, for tests that need to directly verify
+  // it in non-success cases.
+  ErrorCode action_result_code_;
+
+  FakeSystemState fake_system_state_;
+  // "Hash+"
+  const brillo::Blob expected_hash_ = {0x48, 0x61, 0x73, 0x68, 0x2b};
 };
 
 namespace {
@@ -115,11 +128,10 @@
   brillo::FakeMessageLoop loop(nullptr);
   loop.SetAsCurrent();
   ActionProcessor processor;
-  OmahaResponseHandlerActionProcessorDelegate delegate;
-  processor.set_delegate(&delegate);
+  processor.set_delegate(&delegate_);
 
-  ObjectFeederAction<OmahaResponse> feeder_action;
-  feeder_action.set_obj(in);
+  auto feeder_action = std::make_unique<ObjectFeederAction<OmahaResponse>>();
+  feeder_action->set_obj(in);
   if (in.update_exists && in.version != kBadVersion) {
     string expected_hash;
     for (const auto& package : in.packages)
@@ -138,24 +150,29 @@
   EXPECT_CALL(*(fake_system_state_.mock_payload_state()), GetCurrentUrl())
       .WillRepeatedly(Return(current_url));
 
-  action_.reset(new OmahaResponseHandlerAction(
-      &fake_system_state_,
-      (test_deadline_file.empty() ? constants::kOmahaResponseDeadlineFile
-                                  : test_deadline_file)));
-  BondActions(&feeder_action, action_.get());
-  ObjectCollectorAction<InstallPlan> collector_action;
-  BondActions(action_.get(), &collector_action);
-  processor.EnqueueAction(&feeder_action);
-  processor.EnqueueAction(action_.get());
-  processor.EnqueueAction(&collector_action);
+  auto response_handler_action =
+      std::make_unique<OmahaResponseHandlerAction>(&fake_system_state_);
+  if (!test_deadline_file.empty())
+    response_handler_action->deadline_file_ = test_deadline_file;
+
+  auto collector_action =
+      std::make_unique<ObjectCollectorAction<InstallPlan>>();
+
+  BondActions(feeder_action.get(), response_handler_action.get());
+  BondActions(response_handler_action.get(), collector_action.get());
+  processor.EnqueueAction(std::move(feeder_action));
+  processor.EnqueueAction(std::move(response_handler_action));
+  processor.EnqueueAction(std::move(collector_action));
   processor.StartProcessing();
   EXPECT_TRUE(!processor.IsRunning())
       << "Update test to handle non-async actions";
-  if (out)
-    *out = collector_action.object();
-  EXPECT_TRUE(delegate.code_set_);
-  action_result_code_ = delegate.code_;
-  return delegate.code_ == ErrorCode::kSuccess;
+
+  if (out && delegate_.collector_action_install_plan_)
+    *out = *delegate_.collector_action_install_plan_;
+
+  EXPECT_TRUE(delegate_.code_set_);
+  action_result_code_ = delegate_.code_;
+  return delegate_.code_ == ErrorCode::kSuccess;
 }
 
 TEST_F(OmahaResponseHandlerActionTest, SimpleTest) {
@@ -220,6 +237,34 @@
     in.deadline = "some-deadline";
     InstallPlan install_plan;
     fake_system_state_.fake_boot_control()->SetCurrentSlot(0);
+    // Because rollback happened, the deadline shouldn't be written into the
+    // file.
+    EXPECT_CALL(*(fake_system_state_.mock_payload_state()),
+                GetRollbackHappened())
+        .WillOnce(Return(true));
+    EXPECT_TRUE(DoTest(in, test_deadline_file.path(), &install_plan));
+    EXPECT_EQ(in.packages[0].payload_urls[0], install_plan.download_url);
+    EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash);
+    EXPECT_EQ(1U, install_plan.target_slot);
+    string deadline;
+    EXPECT_TRUE(utils::ReadFile(test_deadline_file.path(), &deadline));
+    EXPECT_TRUE(deadline.empty());
+    EXPECT_EQ(in.version, install_plan.version);
+  }
+  {
+    OmahaResponse in;
+    in.update_exists = true;
+    in.version = "a.b.c.d";
+    in.packages.push_back(
+        {.payload_urls = {kLongName}, .size = 12, .hash = kPayloadHashHex});
+    in.more_info_url = "http://more/info";
+    in.prompt = true;
+    in.deadline = "some-deadline";
+    InstallPlan install_plan;
+    fake_system_state_.fake_boot_control()->SetCurrentSlot(0);
+    EXPECT_CALL(*(fake_system_state_.mock_payload_state()),
+                GetRollbackHappened())
+        .WillOnce(Return(false));
     EXPECT_TRUE(DoTest(in, test_deadline_file.path(), &install_plan));
     EXPECT_EQ(in.packages[0].payload_urls[0], install_plan.download_url);
     EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash);
@@ -464,6 +509,109 @@
   EXPECT_TRUE(install_plan.hash_checks_mandatory);
 }
 
+TEST_F(OmahaResponseHandlerActionTest, RollbackTest) {
+  OmahaResponse in;
+  in.update_exists = true;
+  in.packages.push_back({.payload_urls = {"https://RollbackTest"},
+                         .size = 1,
+                         .hash = kPayloadHashHex});
+  in.is_rollback = true;
+  in.rollback_key_version.kernel = 1;
+  in.rollback_key_version.kernel = 2;
+  in.rollback_key_version.firmware_key = 3;
+  in.rollback_key_version.firmware = 4;
+
+  fake_system_state_.fake_hardware()->SetMinKernelKeyVersion(0x00010002);
+  fake_system_state_.fake_hardware()->SetMinFirmwareKeyVersion(0x00030004);
+
+  OmahaRequestParams params(&fake_system_state_);
+  params.set_rollback_allowed(true);
+
+  fake_system_state_.set_request_params(&params);
+  InstallPlan install_plan;
+  EXPECT_TRUE(DoTest(in, "", &install_plan));
+  EXPECT_TRUE(install_plan.is_rollback);
+}
+
+TEST_F(OmahaResponseHandlerActionTest, RollbackKernelVersionErrorTest) {
+  OmahaResponse in;
+  in.update_exists = true;
+  in.packages.push_back({.payload_urls = {"https://RollbackTest"},
+                         .size = 1,
+                         .hash = kPayloadHashHex});
+  in.is_rollback = true;
+  in.rollback_key_version.kernel_key = 1;
+  in.rollback_key_version.kernel = 1;  // This is lower than the minimum.
+  in.rollback_key_version.firmware_key = 3;
+  in.rollback_key_version.firmware = 4;
+
+  fake_system_state_.fake_hardware()->SetMinKernelKeyVersion(0x00010002);
+  fake_system_state_.fake_hardware()->SetMinFirmwareKeyVersion(0x00030004);
+
+  OmahaRequestParams params(&fake_system_state_);
+  params.set_rollback_allowed(true);
+
+  fake_system_state_.set_request_params(&params);
+  InstallPlan install_plan;
+  EXPECT_FALSE(DoTest(in, "", &install_plan));
+}
+
+TEST_F(OmahaResponseHandlerActionTest, RollbackFirmwareVersionErrorTest) {
+  OmahaResponse in;
+  in.update_exists = true;
+  in.packages.push_back({.payload_urls = {"https://RollbackTest"},
+                         .size = 1,
+                         .hash = kPayloadHashHex});
+  in.is_rollback = true;
+  in.rollback_key_version.kernel_key = 1;
+  in.rollback_key_version.kernel = 2;
+  in.rollback_key_version.firmware_key = 3;
+  in.rollback_key_version.firmware = 3;  // This is lower than the minimum.
+
+  fake_system_state_.fake_hardware()->SetMinKernelKeyVersion(0x00010002);
+  fake_system_state_.fake_hardware()->SetMinFirmwareKeyVersion(0x00030004);
+
+  OmahaRequestParams params(&fake_system_state_);
+  params.set_rollback_allowed(true);
+
+  fake_system_state_.set_request_params(&params);
+  InstallPlan install_plan;
+  EXPECT_FALSE(DoTest(in, "", &install_plan));
+}
+
+TEST_F(OmahaResponseHandlerActionTest, RollbackNotRollbackTest) {
+  OmahaResponse in;
+  in.update_exists = true;
+  in.packages.push_back({.payload_urls = {"https://RollbackTest"},
+                         .size = 1,
+                         .hash = kPayloadHashHex});
+  in.is_rollback = false;
+
+  OmahaRequestParams params(&fake_system_state_);
+  params.set_rollback_allowed(true);
+
+  fake_system_state_.set_request_params(&params);
+  InstallPlan install_plan;
+  EXPECT_TRUE(DoTest(in, "", &install_plan));
+  EXPECT_FALSE(install_plan.is_rollback);
+}
+
+TEST_F(OmahaResponseHandlerActionTest, RollbackNotAllowedTest) {
+  OmahaResponse in;
+  in.update_exists = true;
+  in.packages.push_back({.payload_urls = {"https://RollbackTest"},
+                         .size = 1,
+                         .hash = kPayloadHashHex});
+  in.is_rollback = true;
+
+  OmahaRequestParams params(&fake_system_state_);
+  params.set_rollback_allowed(false);
+
+  fake_system_state_.set_request_params(&params);
+  InstallPlan install_plan;
+  EXPECT_FALSE(DoTest(in, "", &install_plan));
+}
+
 TEST_F(OmahaResponseHandlerActionTest, SystemVersionTest) {
   OmahaResponse in;
   in.update_exists = true;
@@ -511,9 +659,8 @@
   EXPECT_EQ(ErrorCode::kOmahaUpdateDeferredPerPolicy, action_result_code_);
   // Verify that DoTest() didn't set the output install plan.
   EXPECT_EQ("", install_plan.version);
-  // Copy the underlying InstallPlan from the Action (like a real Delegate).
-  install_plan = action_->install_plan();
   // Now verify the InstallPlan that was generated.
+  install_plan = *delegate_.response_handler_action_install_plan_;
   EXPECT_EQ(in.packages[0].payload_urls[0], install_plan.download_url);
   EXPECT_EQ(expected_hash_, install_plan.payloads[0].hash);
   EXPECT_EQ(1U, install_plan.target_slot);
diff --git a/p2p_manager_unittest.cc b/p2p_manager_unittest.cc
index 01bdc35..02fd17b 100644
--- a/p2p_manager_unittest.cc
+++ b/p2p_manager_unittest.cc
@@ -340,11 +340,6 @@
 
 // Check that sharing a *new* file works.
 TEST_F(P2PManagerTest, ShareFile) {
-  if (!test_utils::IsXAttrSupported(base::FilePath("/tmp"))) {
-    LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
-                 << "Please update your system to support this feature.";
-    return;
-  }
   const int kP2PTestFileSize = 1000 * 1000;  // 1 MB
 
   EXPECT_TRUE(manager_->FileShare("foo", kP2PTestFileSize));
@@ -362,11 +357,6 @@
 
 // Check that making a shared file visible, does what is expected.
 TEST_F(P2PManagerTest, MakeFileVisible) {
-  if (!test_utils::IsXAttrSupported(base::FilePath("/tmp"))) {
-    LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
-                 << "Please update your system to support this feature.";
-    return;
-  }
   const int kP2PTestFileSize = 1000 * 1000;  // 1 MB
 
   // First, check that it's not visible.
@@ -388,12 +378,6 @@
 
 // Check that we return the right values for existing files in P2P_DIR.
 TEST_F(P2PManagerTest, ExistingFiles) {
-  if (!test_utils::IsXAttrSupported(base::FilePath("/tmp"))) {
-    LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
-                 << "Please update your system to support this feature.";
-    return;
-  }
-
   bool visible;
 
   // Check that errors are returned if the file does not exist
diff --git a/payload_consumer/delta_performer.cc b/payload_consumer/delta_performer.cc
index 171060f..7831c0f 100644
--- a/payload_consumer/delta_performer.cc
+++ b/payload_consumer/delta_performer.cc
@@ -800,7 +800,7 @@
   } else if (major_payload_version_ == kChromeOSMajorPayloadVersion) {
     LOG(INFO) << "Converting update information from old format.";
     PartitionUpdate root_part;
-    root_part.set_partition_name(kLegacyPartitionNameRoot);
+    root_part.set_partition_name(kPartitionNameRoot);
 #ifdef __ANDROID__
     LOG(WARNING) << "Legacy payload major version provided to an Android "
                     "build. Assuming no post-install. Please use major version "
@@ -822,7 +822,7 @@
     partitions_.push_back(std::move(root_part));
 
     PartitionUpdate kern_part;
-    kern_part.set_partition_name(kLegacyPartitionNameKernel);
+    kern_part.set_partition_name(kPartitionNameKernel);
     kern_part.set_run_postinstall(false);
     if (manifest_.has_old_kernel_info()) {
       *kern_part.mutable_old_partition_info() = manifest_.old_kernel_info();
diff --git a/payload_consumer/delta_performer_integration_test.cc b/payload_consumer/delta_performer_integration_test.cc
index 52cc1df..b2fda15 100644
--- a/payload_consumer/delta_performer_integration_test.cc
+++ b/payload_consumer/delta_performer_integration_test.cc
@@ -272,7 +272,7 @@
 
   ASSERT_EQ(0,
             System(base::StringPrintf(
-                "%s -in_file=%s -signature_file=%s -out_file=%s",
+                "%s -in_file=%s -payload_signature_file=%s -out_file=%s",
                 delta_generator_path.c_str(),
                 payload_path.c_str(),
                 sig_files.c_str(),
@@ -499,8 +499,8 @@
     payload_config.version.major = kChromeOSMajorPayloadVersion;
     payload_config.version.minor = minor_version;
     if (!full_rootfs) {
-      payload_config.source.partitions.emplace_back(kLegacyPartitionNameRoot);
-      payload_config.source.partitions.emplace_back(kLegacyPartitionNameKernel);
+      payload_config.source.partitions.emplace_back(kPartitionNameRoot);
+      payload_config.source.partitions.emplace_back(kPartitionNameKernel);
       payload_config.source.partitions.front().path = state->a_img;
       if (!full_kernel)
         payload_config.source.partitions.back().path = state->old_kernel;
@@ -513,9 +513,9 @@
         // Use 1 MiB chunk size for the full unittests.
         payload_config.hard_chunk_size = 1024 * 1024;
     }
-    payload_config.target.partitions.emplace_back(kLegacyPartitionNameRoot);
+    payload_config.target.partitions.emplace_back(kPartitionNameRoot);
     payload_config.target.partitions.back().path = state->b_img;
-    payload_config.target.partitions.emplace_back(kLegacyPartitionNameKernel);
+    payload_config.target.partitions.emplace_back(kPartitionNameKernel);
     payload_config.target.partitions.back().path = state->new_kernel;
     payload_config.target.image_info = new_image_info;
     EXPECT_TRUE(payload_config.target.LoadImageSize());
@@ -717,10 +717,10 @@
   install_plan->target_slot = 1;
 
   InstallPlan::Partition root_part;
-  root_part.name = kLegacyPartitionNameRoot;
+  root_part.name = kPartitionNameRoot;
 
   InstallPlan::Partition kernel_part;
-  kernel_part.name = kLegacyPartitionNameKernel;
+  kernel_part.name = kPartitionNameKernel;
 
   LOG(INFO) << "Setting payload metadata size in Omaha  = "
             << state->metadata_size;
@@ -766,13 +766,13 @@
   }
 
   state->fake_boot_control_.SetPartitionDevice(
-      kLegacyPartitionNameRoot, install_plan->source_slot, state->a_img);
+      kPartitionNameRoot, install_plan->source_slot, state->a_img);
   state->fake_boot_control_.SetPartitionDevice(
-      kLegacyPartitionNameKernel, install_plan->source_slot, state->old_kernel);
+      kPartitionNameKernel, install_plan->source_slot, state->old_kernel);
   state->fake_boot_control_.SetPartitionDevice(
-      kLegacyPartitionNameRoot, install_plan->target_slot, target_root);
+      kPartitionNameRoot, install_plan->target_slot, target_root);
   state->fake_boot_control_.SetPartitionDevice(
-      kLegacyPartitionNameKernel, install_plan->target_slot, target_kernel);
+      kPartitionNameKernel, install_plan->target_slot, target_kernel);
 
   ErrorCode expected_error, actual_error;
   bool continue_writing;
@@ -874,8 +874,8 @@
 
   const auto& partitions = state->install_plan.partitions;
   EXPECT_EQ(2U, partitions.size());
-  EXPECT_EQ(kLegacyPartitionNameRoot, partitions[0].name);
-  EXPECT_EQ(kLegacyPartitionNameKernel, partitions[1].name);
+  EXPECT_EQ(kPartitionNameRoot, partitions[0].name);
+  EXPECT_EQ(kPartitionNameKernel, partitions[1].name);
 
   EXPECT_EQ(kDefaultKernelSize, partitions[1].target_size);
   brillo::Blob expected_new_kernel_hash;
diff --git a/payload_consumer/delta_performer_unittest.cc b/payload_consumer/delta_performer_unittest.cc
index 5052153..b0520e7 100644
--- a/payload_consumer/delta_performer_unittest.cc
+++ b/payload_consumer/delta_performer_unittest.cc
@@ -195,22 +195,22 @@
     PayloadFile payload;
     EXPECT_TRUE(payload.Init(config));
 
-    PartitionConfig old_part(kLegacyPartitionNameRoot);
+    PartitionConfig old_part(kPartitionNameRoot);
     if (minor_version != kFullPayloadMinorVersion) {
       // When generating a delta payload we need to include the old partition
       // information to mark it as a delta payload.
       old_part.path = "/dev/null";
       old_part.size = 0;
     }
-    PartitionConfig new_part(kLegacyPartitionNameRoot);
+    PartitionConfig new_part(kPartitionNameRoot);
     new_part.path = "/dev/zero";
     new_part.size = 1234;
 
     payload.AddPartition(old_part, new_part, aops);
 
     // We include a kernel partition without operations.
-    old_part.name = kLegacyPartitionNameKernel;
-    new_part.name = kLegacyPartitionNameKernel;
+    old_part.name = kPartitionNameKernel;
+    new_part.name = kPartitionNameKernel;
     new_part.size = 0;
     payload.AddPartition(old_part, new_part, {});
 
@@ -270,13 +270,13 @@
     // We installed the operations only in the rootfs partition, but the
     // delta performer needs to access all the partitions.
     fake_boot_control_.SetPartitionDevice(
-        kLegacyPartitionNameRoot, install_plan_.target_slot, new_part.path());
+        kPartitionNameRoot, install_plan_.target_slot, new_part.path());
     fake_boot_control_.SetPartitionDevice(
-        kLegacyPartitionNameRoot, install_plan_.source_slot, source_path);
+        kPartitionNameRoot, install_plan_.source_slot, source_path);
     fake_boot_control_.SetPartitionDevice(
-        kLegacyPartitionNameKernel, install_plan_.target_slot, "/dev/null");
+        kPartitionNameKernel, install_plan_.target_slot, "/dev/null");
     fake_boot_control_.SetPartitionDevice(
-        kLegacyPartitionNameKernel, install_plan_.source_slot, "/dev/null");
+        kPartitionNameKernel, install_plan_.source_slot, "/dev/null");
 
     EXPECT_EQ(expect_success,
               performer_.Write(payload_data.data(), payload_data.size()));
diff --git a/payload_consumer/download_action.cc b/payload_consumer/download_action.cc
index 1654c2a..ab9f2e8 100644
--- a/payload_consumer/download_action.cc
+++ b/payload_consumer/download_action.cc
@@ -318,7 +318,7 @@
   bytes_received_ = offset;
 }
 
-void DownloadAction::ReceivedBytes(HttpFetcher* fetcher,
+bool DownloadAction::ReceivedBytes(HttpFetcher* fetcher,
                                    const void* bytes,
                                    size_t length) {
   // Note that bytes_received_ is the current offset.
@@ -345,7 +345,7 @@
     // the TransferTerminated callback. Otherwise, this and the HTTP fetcher
     // objects may get destroyed before all callbacks are complete.
     TerminateProcessing();
-    return;
+    return false;
   }
 
   // Call p2p_manager_->FileMakeVisible() when we've successfully
@@ -356,6 +356,7 @@
     system_state_->p2p_manager()->FileMakeVisible(p2p_file_id_);
     p2p_visible_ = true;
   }
+  return true;
 }
 
 void DownloadAction::TransferComplete(HttpFetcher* fetcher, bool successful) {
diff --git a/payload_consumer/download_action.h b/payload_consumer/download_action.h
index 6e6f057..028a99a 100644
--- a/payload_consumer/download_action.h
+++ b/payload_consumer/download_action.h
@@ -97,8 +97,9 @@
   int GetHTTPResponseCode() { return http_fetcher_->http_response_code(); }
 
   // HttpFetcherDelegate methods (see http_fetcher.h)
-  void ReceivedBytes(HttpFetcher* fetcher,
-                     const void* bytes, size_t length) override;
+  bool ReceivedBytes(HttpFetcher* fetcher,
+                     const void* bytes,
+                     size_t length) override;
   void SeekToOffset(off_t offset) override;
   void TransferComplete(HttpFetcher* fetcher, bool successful) override;
   void TransferTerminated(HttpFetcher* fetcher) override;
diff --git a/payload_consumer/download_action_unittest.cc b/payload_consumer/download_action_unittest.cc
index 5dac550..84673c8 100644
--- a/payload_consumer/download_action_unittest.cc
+++ b/payload_consumer/download_action_unittest.cc
@@ -64,9 +64,8 @@
 
 class DownloadActionTestProcessorDelegate : public ActionProcessorDelegate {
  public:
-  explicit DownloadActionTestProcessorDelegate(ErrorCode expected_code)
-      : processing_done_called_(false),
-        expected_code_(expected_code) {}
+  DownloadActionTestProcessorDelegate()
+      : processing_done_called_(false), expected_code_(ErrorCode::kSuccess) {}
   ~DownloadActionTestProcessorDelegate() override {
     EXPECT_TRUE(processing_done_called_);
   }
@@ -90,6 +89,7 @@
     const string type = action->Type();
     if (type == DownloadAction::StaticType()) {
       EXPECT_EQ(expected_code_, code);
+      p2p_file_id_ = static_cast<DownloadAction*>(action)->p2p_file_id();
     } else {
       EXPECT_EQ(ErrorCode::kSuccess, code);
     }
@@ -99,6 +99,7 @@
   brillo::Blob expected_data_;
   bool processing_done_called_;
   ErrorCode expected_code_;
+  string p2p_file_id_;
 };
 
 class TestDirectFileWriter : public DirectFileWriter {
@@ -154,25 +155,26 @@
       install_plan.source_slot, true);
   fake_system_state.fake_boot_control()->SetSlotBootable(
       install_plan.target_slot, true);
-  ObjectFeederAction<InstallPlan> feeder_action;
-  feeder_action.set_obj(install_plan);
+  auto feeder_action = std::make_unique<ObjectFeederAction<InstallPlan>>();
+  feeder_action->set_obj(install_plan);
   MockPrefs prefs;
   MockHttpFetcher* http_fetcher = new MockHttpFetcher(data.data(),
                                                       data.size(),
                                                       nullptr);
   // takes ownership of passed in HttpFetcher
-  DownloadAction download_action(&prefs,
-                                 fake_system_state.boot_control(),
-                                 fake_system_state.hardware(),
-                                 &fake_system_state,
-                                 http_fetcher,
-                                 false /* interactive */);
-  download_action.SetTestFileWriter(&writer);
-  BondActions(&feeder_action, &download_action);
+  auto download_action =
+      std::make_unique<DownloadAction>(&prefs,
+                                       fake_system_state.boot_control(),
+                                       fake_system_state.hardware(),
+                                       &fake_system_state,
+                                       http_fetcher,
+                                       false /* interactive */);
+  download_action->SetTestFileWriter(&writer);
+  BondActions(feeder_action.get(), download_action.get());
   MockDownloadActionDelegate download_delegate;
   if (use_download_delegate) {
     InSequence s;
-    download_action.set_delegate(&download_delegate);
+    download_action->set_delegate(&download_delegate);
     if (data.size() > kMockHttpFetcherChunkSize)
       EXPECT_CALL(download_delegate,
                   BytesReceived(_, kMockHttpFetcherChunkSize, _));
@@ -180,16 +182,15 @@
     EXPECT_CALL(download_delegate, DownloadComplete())
         .Times(fail_write == 0 ? 1 : 0);
   }
-  ErrorCode expected_code = ErrorCode::kSuccess;
-  if (fail_write > 0)
-    expected_code = ErrorCode::kDownloadWriteError;
-  DownloadActionTestProcessorDelegate delegate(expected_code);
+  DownloadActionTestProcessorDelegate delegate;
+  delegate.expected_code_ =
+      (fail_write > 0) ? ErrorCode::kDownloadWriteError : ErrorCode::kSuccess;
   delegate.expected_data_ = brillo::Blob(data.begin() + 1, data.end());
   delegate.path_ = output_temp_file.path();
   ActionProcessor processor;
   processor.set_delegate(&delegate);
-  processor.EnqueueAction(&feeder_action);
-  processor.EnqueueAction(&download_action);
+  processor.EnqueueAction(std::move(feeder_action));
+  processor.EnqueueAction(std::move(download_action));
 
   loop.PostTask(FROM_HERE,
                 base::Bind(&StartProcessorInRunLoop, &processor, http_fetcher));
@@ -271,24 +272,25 @@
         {.size = size, .type = InstallPayloadType::kFull});
     total_expected_download_size += size;
   }
-  ObjectFeederAction<InstallPlan> feeder_action;
-  feeder_action.set_obj(install_plan);
+  auto feeder_action = std::make_unique<ObjectFeederAction<InstallPlan>>();
+  feeder_action->set_obj(install_plan);
   MockPrefs prefs;
   MockHttpFetcher* http_fetcher = new MockHttpFetcher(
       payload_datas[0].data(), payload_datas[0].size(), nullptr);
   // takes ownership of passed in HttpFetcher
-  DownloadAction download_action(&prefs,
-                                 fake_system_state.boot_control(),
-                                 fake_system_state.hardware(),
-                                 &fake_system_state,
-                                 http_fetcher,
-                                 false /* interactive */);
-  download_action.SetTestFileWriter(&mock_file_writer);
-  BondActions(&feeder_action, &download_action);
+  auto download_action =
+      std::make_unique<DownloadAction>(&prefs,
+                                       fake_system_state.boot_control(),
+                                       fake_system_state.hardware(),
+                                       &fake_system_state,
+                                       http_fetcher,
+                                       false /* interactive */);
+  download_action->SetTestFileWriter(&mock_file_writer);
+  BondActions(feeder_action.get(), download_action.get());
   MockDownloadActionDelegate download_delegate;
   {
     InSequence s;
-    download_action.set_delegate(&download_delegate);
+    download_action->set_delegate(&download_delegate);
     // these are hand-computed based on the payloads specified above
     EXPECT_CALL(download_delegate,
                 BytesReceived(kMockHttpFetcherChunkSize,
@@ -320,8 +322,8 @@
                               total_expected_download_size));
   }
   ActionProcessor processor;
-  processor.EnqueueAction(&feeder_action);
-  processor.EnqueueAction(&download_action);
+  processor.EnqueueAction(std::move(feeder_action));
+  processor.EnqueueAction(std::move(download_action));
 
   loop.PostTask(
       FROM_HERE,
@@ -360,31 +362,31 @@
     EXPECT_EQ(0, writer.Open(temp_file.path().c_str(), O_WRONLY | O_CREAT, 0));
 
     // takes ownership of passed in HttpFetcher
-    ObjectFeederAction<InstallPlan> feeder_action;
+    auto feeder_action = std::make_unique<ObjectFeederAction<InstallPlan>>();
     InstallPlan install_plan;
     install_plan.payloads.resize(1);
-    feeder_action.set_obj(install_plan);
+    feeder_action->set_obj(install_plan);
     FakeSystemState fake_system_state_;
     MockPrefs prefs;
-    DownloadAction download_action(
+    auto download_action = std::make_unique<DownloadAction>(
         &prefs,
         fake_system_state_.boot_control(),
         fake_system_state_.hardware(),
         &fake_system_state_,
         new MockHttpFetcher(data.data(), data.size(), nullptr),
         false /* interactive */);
-    download_action.SetTestFileWriter(&writer);
+    download_action->SetTestFileWriter(&writer);
     MockDownloadActionDelegate download_delegate;
     if (use_download_delegate) {
-      download_action.set_delegate(&download_delegate);
+      download_action->set_delegate(&download_delegate);
       EXPECT_CALL(download_delegate, BytesReceived(_, _, _)).Times(0);
     }
     TerminateEarlyTestProcessorDelegate delegate;
     ActionProcessor processor;
     processor.set_delegate(&delegate);
-    processor.EnqueueAction(&feeder_action);
-    processor.EnqueueAction(&download_action);
-    BondActions(&feeder_action, &download_action);
+    BondActions(feeder_action.get(), download_action.get());
+    processor.EnqueueAction(std::move(feeder_action));
+    processor.EnqueueAction(std::move(download_action));
 
     loop.PostTask(FROM_HERE,
                   base::Bind(&TerminateEarlyTestStarter, &processor));
@@ -422,22 +424,21 @@
 // This is a simple Action class for testing.
 class DownloadActionTestAction : public Action<DownloadActionTestAction> {
  public:
-  DownloadActionTestAction() : did_run_(false) {}
+  DownloadActionTestAction() = default;
   typedef InstallPlan InputObjectType;
   typedef InstallPlan OutputObjectType;
   ActionPipe<InstallPlan>* in_pipe() { return in_pipe_.get(); }
   ActionPipe<InstallPlan>* out_pipe() { return out_pipe_.get(); }
   ActionProcessor* processor() { return processor_; }
   void PerformAction() {
-    did_run_ = true;
     ASSERT_TRUE(HasInputObject());
     EXPECT_TRUE(expected_input_object_ == GetInputObject());
     ASSERT_TRUE(processor());
     processor()->ActionComplete(this, ErrorCode::kSuccess);
   }
-  string Type() const { return "DownloadActionTestAction"; }
+  static std::string StaticType() { return "DownloadActionTestAction"; }
+  string Type() const { return StaticType(); }
   InstallPlan expected_input_object_;
-  bool did_run_;
 };
 
 namespace {
@@ -446,9 +447,19 @@
 // only by the test PassObjectOutTest.
 class PassObjectOutTestProcessorDelegate : public ActionProcessorDelegate {
  public:
-  void ProcessingDone(const ActionProcessor* processor, ErrorCode code) {
+  void ProcessingDone(const ActionProcessor* processor,
+                      ErrorCode code) override {
     brillo::MessageLoop::current()->BreakLoop();
   }
+  void ActionCompleted(ActionProcessor* processor,
+                       AbstractAction* action,
+                       ErrorCode code) override {
+    if (action->Type() == DownloadActionTestAction::StaticType()) {
+      did_test_action_run_ = true;
+    }
+  }
+
+  bool did_test_action_run_ = false;
 };
 
 }  // namespace
@@ -465,29 +476,30 @@
   install_plan.payloads.push_back({.size = 1});
   EXPECT_TRUE(
       HashCalculator::RawHashOfData({'x'}, &install_plan.payloads[0].hash));
-  ObjectFeederAction<InstallPlan> feeder_action;
-  feeder_action.set_obj(install_plan);
+  auto feeder_action = std::make_unique<ObjectFeederAction<InstallPlan>>();
+  feeder_action->set_obj(install_plan);
   MockPrefs prefs;
   FakeSystemState fake_system_state_;
-  DownloadAction download_action(&prefs,
-                                 fake_system_state_.boot_control(),
-                                 fake_system_state_.hardware(),
-                                 &fake_system_state_,
-                                 new MockHttpFetcher("x", 1, nullptr),
-                                 false /* interactive */);
-  download_action.SetTestFileWriter(&writer);
+  auto download_action =
+      std::make_unique<DownloadAction>(&prefs,
+                                       fake_system_state_.boot_control(),
+                                       fake_system_state_.hardware(),
+                                       &fake_system_state_,
+                                       new MockHttpFetcher("x", 1, nullptr),
+                                       false /* interactive */);
+  download_action->SetTestFileWriter(&writer);
 
-  DownloadActionTestAction test_action;
-  test_action.expected_input_object_ = install_plan;
-  BondActions(&feeder_action, &download_action);
-  BondActions(&download_action, &test_action);
+  auto test_action = std::make_unique<DownloadActionTestAction>();
+  test_action->expected_input_object_ = install_plan;
+  BondActions(feeder_action.get(), download_action.get());
+  BondActions(download_action.get(), test_action.get());
 
   ActionProcessor processor;
   PassObjectOutTestProcessorDelegate delegate;
   processor.set_delegate(&delegate);
-  processor.EnqueueAction(&feeder_action);
-  processor.EnqueueAction(&download_action);
-  processor.EnqueueAction(&test_action);
+  processor.EnqueueAction(std::move(feeder_action));
+  processor.EnqueueAction(std::move(download_action));
+  processor.EnqueueAction(std::move(test_action));
 
   loop.PostTask(
       FROM_HERE,
@@ -497,7 +509,7 @@
   loop.Run();
   EXPECT_FALSE(loop.PendingTasks());
 
-  EXPECT_EQ(true, test_action.did_run_);
+  EXPECT_EQ(true, delegate.did_test_action_run_);
 }
 
 // Test fixture for P2P tests.
@@ -552,43 +564,44 @@
     install_plan.payloads.push_back(
         {.size = data_.length(),
          .hash = {'1', '2', '3', '4', 'h', 'a', 's', 'h'}});
-    ObjectFeederAction<InstallPlan> feeder_action;
-    feeder_action.set_obj(install_plan);
+    auto feeder_action = std::make_unique<ObjectFeederAction<InstallPlan>>();
+    feeder_action->set_obj(install_plan);
     MockPrefs prefs;
-    http_fetcher_ = new MockHttpFetcher(data_.c_str(),
-                                        data_.length(),
-                                        nullptr);
     // Note that DownloadAction takes ownership of the passed in HttpFetcher.
-    download_action_.reset(new DownloadAction(&prefs,
-                                              fake_system_state_.boot_control(),
-                                              fake_system_state_.hardware(),
-                                              &fake_system_state_,
-                                              http_fetcher_,
-                                              false /* interactive */));
-    download_action_->SetTestFileWriter(&writer);
-    BondActions(&feeder_action, download_action_.get());
-    DownloadActionTestProcessorDelegate delegate(ErrorCode::kSuccess);
-    delegate.expected_data_ = brillo::Blob(data_.begin() + start_at_offset_,
-                                           data_.end());
-    delegate.path_ = output_temp_file.path();
-    processor_.set_delegate(&delegate);
-    processor_.EnqueueAction(&feeder_action);
-    processor_.EnqueueAction(download_action_.get());
+    auto download_action = std::make_unique<DownloadAction>(
+        &prefs,
+        fake_system_state_.boot_control(),
+        fake_system_state_.hardware(),
+        &fake_system_state_,
+        new MockHttpFetcher(data_.c_str(), data_.length(), nullptr),
+        false /* interactive */);
+    auto http_fetcher = download_action->http_fetcher();
+    download_action->SetTestFileWriter(&writer);
+    BondActions(feeder_action.get(), download_action.get());
+    delegate_.expected_data_ =
+        brillo::Blob(data_.begin() + start_at_offset_, data_.end());
+    delegate_.path_ = output_temp_file.path();
+    processor_.set_delegate(&delegate_);
+    processor_.EnqueueAction(std::move(feeder_action));
+    processor_.EnqueueAction(std::move(download_action));
 
-    loop_.PostTask(FROM_HERE, base::Bind(
-        &P2PDownloadActionTest::StartProcessorInRunLoopForP2P,
-        base::Unretained(this)));
+    loop_.PostTask(
+        FROM_HERE,
+        base::Bind(
+            [](P2PDownloadActionTest* action_test, HttpFetcher* http_fetcher) {
+              action_test->processor_.StartProcessing();
+              http_fetcher->SetOffset(action_test->start_at_offset_);
+            },
+            base::Unretained(this),
+            base::Unretained(http_fetcher)));
     loop_.Run();
   }
 
   // Mainloop used to make StartDownload() synchronous.
   brillo::FakeMessageLoop loop_{nullptr};
 
-  // The DownloadAction instance under test.
-  unique_ptr<DownloadAction> download_action_;
-
-  // The HttpFetcher used in the test.
-  MockHttpFetcher* http_fetcher_;
+  // Delegate that is passed to the ActionProcessor.
+  DownloadActionTestProcessorDelegate delegate_;
 
   // The P2PManager used in the test.
   unique_ptr<P2PManager> p2p_manager_;
@@ -603,12 +616,6 @@
   string data_;
 
  private:
-  // Callback used in StartDownload() method.
-  void StartProcessorInRunLoopForP2P() {
-    processor_.StartProcessing();
-    download_action_->http_fetcher()->SetOffset(start_at_offset_);
-  }
-
   // The requested starting offset passed to SetupDownload().
   off_t start_at_offset_;
 
@@ -616,17 +623,11 @@
 };
 
 TEST_F(P2PDownloadActionTest, IsWrittenTo) {
-  if (!test_utils::IsXAttrSupported(FilePath("/tmp"))) {
-    LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
-                 << "Please update your system to support this feature.";
-    return;
-  }
-
   SetupDownload(0);     // starting_offset
   StartDownload(true);  // use_p2p_to_share
 
   // Check the p2p file and its content matches what was sent.
-  string file_id = download_action_->p2p_file_id();
+  string file_id = delegate_.p2p_file_id_;
   EXPECT_NE("", file_id);
   EXPECT_EQ(static_cast<int>(data_.length()),
             p2p_manager_->FileGetSize(file_id));
@@ -639,28 +640,16 @@
 }
 
 TEST_F(P2PDownloadActionTest, DeleteIfHoleExists) {
-  if (!test_utils::IsXAttrSupported(FilePath("/tmp"))) {
-    LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
-                 << "Please update your system to support this feature.";
-    return;
-  }
-
   SetupDownload(1000);  // starting_offset
   StartDownload(true);  // use_p2p_to_share
 
   // DownloadAction should convey that the file is not being shared.
   // and that we don't have any p2p files.
-  EXPECT_EQ(download_action_->p2p_file_id(), "");
+  EXPECT_EQ(delegate_.p2p_file_id_, "");
   EXPECT_EQ(p2p_manager_->CountSharedFiles(), 0);
 }
 
 TEST_F(P2PDownloadActionTest, CanAppend) {
-  if (!test_utils::IsXAttrSupported(FilePath("/tmp"))) {
-    LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
-                 << "Please update your system to support this feature.";
-    return;
-  }
-
   SetupDownload(1000);  // starting_offset
 
   // Prepare the file with existing data before starting to write to
@@ -678,7 +667,7 @@
 
   // DownloadAction should convey the same file_id and the file should
   // have the expected size.
-  EXPECT_EQ(download_action_->p2p_file_id(), file_id);
+  EXPECT_EQ(delegate_.p2p_file_id_, file_id);
   EXPECT_EQ(static_cast<ssize_t>(data_.length()),
             p2p_manager_->FileGetSize(file_id));
   EXPECT_EQ(static_cast<ssize_t>(data_.length()),
@@ -693,12 +682,6 @@
 }
 
 TEST_F(P2PDownloadActionTest, DeletePartialP2PFileIfResumingWithoutP2P) {
-  if (!test_utils::IsXAttrSupported(FilePath("/tmp"))) {
-    LOG(WARNING) << "Skipping test because /tmp does not support xattr. "
-                 << "Please update your system to support this feature.";
-    return;
-  }
-
   SetupDownload(1000);  // starting_offset
 
   // Prepare the file with all existing data before starting to write
diff --git a/payload_consumer/filesystem_verifier_action.cc b/payload_consumer/filesystem_verifier_action.cc
index 5edde9e..6a379e5 100644
--- a/payload_consumer/filesystem_verifier_action.cc
+++ b/payload_consumer/filesystem_verifier_action.cc
@@ -70,10 +70,6 @@
   Cleanup(ErrorCode::kSuccess);  // error code is ignored if canceled_ is true.
 }
 
-bool FilesystemVerifierAction::IsCleanupPending() const {
-  return src_stream_ != nullptr;
-}
-
 void FilesystemVerifierAction::Cleanup(ErrorCode code) {
   src_stream_.reset();
   // This memory is not used anymore.
diff --git a/payload_consumer/filesystem_verifier_action.h b/payload_consumer/filesystem_verifier_action.h
index 616f7b7..a21fc2a 100644
--- a/payload_consumer/filesystem_verifier_action.h
+++ b/payload_consumer/filesystem_verifier_action.h
@@ -20,6 +20,7 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 
+#include <memory>
 #include <string>
 #include <vector>
 
@@ -54,17 +55,12 @@
   void PerformAction() override;
   void TerminateProcessing() override;
 
-  // Used for testing. Return true if Cleanup() has not yet been called due
-  // to a callback upon the completion or cancellation of the verifier action.
-  // A test should wait until IsCleanupPending() returns false before
-  // terminating the main loop.
-  bool IsCleanupPending() const;
-
   // Debugging/logging
   static std::string StaticType() { return "FilesystemVerifierAction"; }
   std::string Type() const override { return StaticType(); }
 
  private:
+  friend class FilesystemVerifierActionTestDelegate;
   // Starts the hashing of the current partition. If there aren't any partitions
   // remaining to be hashed, it finishes the action.
   void StartPartitionHashing();
diff --git a/payload_consumer/filesystem_verifier_action_unittest.cc b/payload_consumer/filesystem_verifier_action_unittest.cc
index fd1d084..91a7da4 100644
--- a/payload_consumer/filesystem_verifier_action_unittest.cc
+++ b/payload_consumer/filesystem_verifier_action_unittest.cc
@@ -18,8 +18,10 @@
 
 #include <fcntl.h>
 
+#include <memory>
 #include <set>
 #include <string>
+#include <utility>
 #include <vector>
 
 #include <base/bind.h>
@@ -61,27 +63,14 @@
 
 class FilesystemVerifierActionTestDelegate : public ActionProcessorDelegate {
  public:
-  explicit FilesystemVerifierActionTestDelegate(
-      FilesystemVerifierAction* action)
-      : action_(action), ran_(false), code_(ErrorCode::kError) {}
-  void ExitMainLoop() {
-    // We need to wait for the Action to call Cleanup.
-    if (action_->IsCleanupPending()) {
-      LOG(INFO) << "Waiting for Cleanup() to be called.";
-      MessageLoop::current()->PostDelayedTask(
-          FROM_HERE,
-          base::Bind(&FilesystemVerifierActionTestDelegate::ExitMainLoop,
-                     base::Unretained(this)),
-          base::TimeDelta::FromMilliseconds(100));
-    } else {
-      MessageLoop::current()->BreakLoop();
-    }
-  }
+  FilesystemVerifierActionTestDelegate()
+      : ran_(false), code_(ErrorCode::kError) {}
+
   void ProcessingDone(const ActionProcessor* processor, ErrorCode code) {
-    ExitMainLoop();
+    MessageLoop::current()->BreakLoop();
   }
   void ProcessingStopped(const ActionProcessor* processor) {
-    ExitMainLoop();
+    MessageLoop::current()->BreakLoop();
   }
   void ActionCompleted(ActionProcessor* processor,
                        AbstractAction* action,
@@ -89,27 +78,24 @@
     if (action->Type() == FilesystemVerifierAction::StaticType()) {
       ran_ = true;
       code_ = code;
+      EXPECT_FALSE(static_cast<FilesystemVerifierAction*>(action)->src_stream_);
+    } else if (action->Type() ==
+               ObjectCollectorAction<InstallPlan>::StaticType()) {
+      auto collector_action =
+          static_cast<ObjectCollectorAction<InstallPlan>*>(action);
+      install_plan_.reset(new InstallPlan(collector_action->object()));
     }
   }
   bool ran() const { return ran_; }
   ErrorCode code() const { return code_; }
 
+  std::unique_ptr<InstallPlan> install_plan_;
+
  private:
-  FilesystemVerifierAction* action_;
   bool ran_;
   ErrorCode code_;
 };
 
-void StartProcessorInRunLoop(ActionProcessor* processor,
-                             FilesystemVerifierAction* filesystem_copier_action,
-                             bool terminate_early) {
-  processor->StartProcessing();
-  if (terminate_early) {
-    EXPECT_NE(nullptr, filesystem_copier_action);
-    processor->StopProcessing();
-  }
-}
-
 bool FilesystemVerifierActionTest::DoTest(bool terminate_early,
                                           bool hash_fail) {
   test_utils::ScopedTempFile a_loop_file("a_loop_file.XXXXXX");
@@ -158,27 +144,32 @@
   }
   install_plan.partitions = {part};
 
+  auto feeder_action = std::make_unique<ObjectFeederAction<InstallPlan>>();
+  feeder_action->set_obj(install_plan);
+  auto copier_action = std::make_unique<FilesystemVerifierAction>();
+  auto collector_action =
+      std::make_unique<ObjectCollectorAction<InstallPlan>>();
+
+  BondActions(feeder_action.get(), copier_action.get());
+  BondActions(copier_action.get(), collector_action.get());
+
   ActionProcessor processor;
-
-  ObjectFeederAction<InstallPlan> feeder_action;
-  FilesystemVerifierAction copier_action;
-  ObjectCollectorAction<InstallPlan> collector_action;
-
-  BondActions(&feeder_action, &copier_action);
-  BondActions(&copier_action, &collector_action);
-
-  FilesystemVerifierActionTestDelegate delegate(&copier_action);
+  FilesystemVerifierActionTestDelegate delegate;
   processor.set_delegate(&delegate);
-  processor.EnqueueAction(&feeder_action);
-  processor.EnqueueAction(&copier_action);
-  processor.EnqueueAction(&collector_action);
+  processor.EnqueueAction(std::move(feeder_action));
+  processor.EnqueueAction(std::move(copier_action));
+  processor.EnqueueAction(std::move(collector_action));
 
-  feeder_action.set_obj(install_plan);
-
-  loop_.PostTask(FROM_HERE, base::Bind(&StartProcessorInRunLoop,
-                                       &processor,
-                                       &copier_action,
-                                       terminate_early));
+  loop_.PostTask(FROM_HERE,
+                 base::Bind(
+                     [](ActionProcessor* processor, bool terminate_early) {
+                       processor->StartProcessing();
+                       if (terminate_early) {
+                         processor->StopProcessing();
+                       }
+                     },
+                     base::Unretained(&processor),
+                     terminate_early));
   loop_.Run();
 
   if (!terminate_early) {
@@ -207,7 +198,7 @@
   EXPECT_TRUE(is_a_file_reading_eq);
   success = success && is_a_file_reading_eq;
 
-  bool is_install_plan_eq = (collector_action.object() == install_plan);
+  bool is_install_plan_eq = (*delegate.install_plan_ == install_plan);
   EXPECT_TRUE(is_install_plan_eq);
   success = success && is_install_plan_eq;
   return success;
@@ -233,13 +224,14 @@
 
   processor.set_delegate(&delegate);
 
-  FilesystemVerifierAction copier_action;
-  ObjectCollectorAction<InstallPlan> collector_action;
+  auto copier_action = std::make_unique<FilesystemVerifierAction>();
+  auto collector_action =
+      std::make_unique<ObjectCollectorAction<InstallPlan>>();
 
-  BondActions(&copier_action, &collector_action);
+  BondActions(copier_action.get(), collector_action.get());
 
-  processor.EnqueueAction(&copier_action);
-  processor.EnqueueAction(&collector_action);
+  processor.EnqueueAction(std::move(copier_action));
+  processor.EnqueueAction(std::move(collector_action));
   processor.StartProcessing();
   EXPECT_FALSE(processor.IsRunning());
   EXPECT_TRUE(delegate.ran_);
@@ -252,7 +244,6 @@
 
   processor.set_delegate(&delegate);
 
-  ObjectFeederAction<InstallPlan> feeder_action;
   InstallPlan install_plan;
   InstallPlan::Partition part;
   part.name = "nope";
@@ -260,15 +251,18 @@
   part.target_path = "/no/such/file";
   install_plan.partitions = {part};
 
-  feeder_action.set_obj(install_plan);
-  FilesystemVerifierAction verifier_action;
-  ObjectCollectorAction<InstallPlan> collector_action;
+  auto feeder_action = std::make_unique<ObjectFeederAction<InstallPlan>>();
+  auto verifier_action = std::make_unique<FilesystemVerifierAction>();
+  auto collector_action =
+      std::make_unique<ObjectCollectorAction<InstallPlan>>();
 
-  BondActions(&verifier_action, &collector_action);
+  feeder_action->set_obj(install_plan);
 
-  processor.EnqueueAction(&feeder_action);
-  processor.EnqueueAction(&verifier_action);
-  processor.EnqueueAction(&collector_action);
+  BondActions(verifier_action.get(), collector_action.get());
+
+  processor.EnqueueAction(std::move(feeder_action));
+  processor.EnqueueAction(std::move(verifier_action));
+  processor.EnqueueAction(std::move(collector_action));
   processor.StartProcessing();
   EXPECT_FALSE(processor.IsRunning());
   EXPECT_TRUE(delegate.ran_);
diff --git a/payload_consumer/install_plan.h b/payload_consumer/install_plan.h
index 5cdfbc1..929cad3 100644
--- a/payload_consumer/install_plan.h
+++ b/payload_consumer/install_plan.h
@@ -127,6 +127,9 @@
   // False otherwise.
   bool run_post_install{true};
 
+  // True if this update is a rollback.
+  bool is_rollback{false};
+
   // If not blank, a base-64 encoded representation of the PEM-encoded
   // public key in the response.
   std::string public_key_rsa;
diff --git a/payload_consumer/payload_constants.cc b/payload_consumer/payload_constants.cc
index 797e76d..6e7cd00 100644
--- a/payload_consumer/payload_constants.cc
+++ b/payload_consumer/payload_constants.cc
@@ -36,8 +36,8 @@
 
 const uint64_t kMaxPayloadHeaderSize = 24;
 
-const char kLegacyPartitionNameKernel[] = "boot";
-const char kLegacyPartitionNameRoot[] = "system";
+const char kPartitionNameKernel[] = "kernel";
+const char kPartitionNameRoot[] = "root";
 
 const char kDeltaMagic[4] = {'C', 'r', 'A', 'U'};
 
diff --git a/payload_consumer/payload_constants.h b/payload_consumer/payload_constants.h
index 43c3137..0833484 100644
--- a/payload_consumer/payload_constants.h
+++ b/payload_consumer/payload_constants.h
@@ -63,8 +63,8 @@
 // The kernel and rootfs partition names used by the BootControlInterface when
 // handling update payloads with a major version 1. The names of the updated
 // partitions are include in the payload itself for major version 2.
-extern const char kLegacyPartitionNameKernel[];
-extern const char kLegacyPartitionNameRoot[];
+extern const char kPartitionNameKernel[];
+extern const char kPartitionNameRoot[];
 
 extern const char kBspatchPath[];
 extern const char kDeltaMagic[4];
diff --git a/payload_consumer/postinstall_runner_action.cc b/payload_consumer/postinstall_runner_action.cc
index b6af131..83d910f 100644
--- a/payload_consumer/postinstall_runner_action.cc
+++ b/payload_consumer/postinstall_runner_action.cc
@@ -57,7 +57,8 @@
   CHECK(HasInputObject());
   install_plan_ = GetInputObject();
 
-  if (install_plan_.powerwash_required) {
+  // Currently we're always powerwashing when rolling back.
+  if (install_plan_.powerwash_required || install_plan_.is_rollback) {
     if (hardware_->SchedulePowerwash()) {
       powerwash_scheduled_ = true;
     } else {
diff --git a/payload_consumer/postinstall_runner_action_unittest.cc b/payload_consumer/postinstall_runner_action_unittest.cc
index f15171b..8381472 100644
--- a/payload_consumer/postinstall_runner_action_unittest.cc
+++ b/payload_consumer/postinstall_runner_action_unittest.cc
@@ -22,6 +22,7 @@
 
 #include <memory>
 #include <string>
+#include <utility>
 
 #include <base/bind.h>
 #include <base/files/file_util.h>
@@ -38,6 +39,7 @@
 #include "update_engine/common/fake_hardware.h"
 #include "update_engine/common/test_utils.h"
 #include "update_engine/common/utils.h"
+#include "update_engine/mock_payload_state.h"
 
 using brillo::MessageLoop;
 using chromeos_update_engine::test_utils::ScopedLoopbackDeviceBinder;
@@ -94,9 +96,10 @@
   // Setup an action processor and run the PostinstallRunnerAction with a single
   // partition |device_path|, running the |postinstall_program| command from
   // there.
-  void RunPosinstallAction(const string& device_path,
-                           const string& postinstall_program,
-                           bool powerwash_required);
+  void RunPostinstallAction(const string& device_path,
+                            const string& postinstall_program,
+                            bool powerwash_required,
+                            bool is_rollback);
 
  public:
   void ResumeRunningAction() {
@@ -162,13 +165,14 @@
   ActionProcessor* processor_{nullptr};
 };
 
-void PostinstallRunnerActionTest::RunPosinstallAction(
+void PostinstallRunnerActionTest::RunPostinstallAction(
     const string& device_path,
     const string& postinstall_program,
-    bool powerwash_required) {
+    bool powerwash_required,
+    bool is_rollback) {
   ActionProcessor processor;
   processor_ = &processor;
-  ObjectFeederAction<InstallPlan> feeder_action;
+  auto feeder_action = std::make_unique<ObjectFeederAction<InstallPlan>>();
   InstallPlan::Partition part;
   part.name = "part";
   part.target_path = device_path;
@@ -178,16 +182,19 @@
   install_plan.partitions = {part};
   install_plan.download_url = "http://127.0.0.1:8080/update";
   install_plan.powerwash_required = powerwash_required;
-  feeder_action.set_obj(install_plan);
-  PostinstallRunnerAction runner_action(&fake_boot_control_, &fake_hardware_);
-  postinstall_action_ = &runner_action;
-  runner_action.set_delegate(setup_action_delegate_);
-  BondActions(&feeder_action, &runner_action);
-  ObjectCollectorAction<InstallPlan> collector_action;
-  BondActions(&runner_action, &collector_action);
-  processor.EnqueueAction(&feeder_action);
-  processor.EnqueueAction(&runner_action);
-  processor.EnqueueAction(&collector_action);
+  install_plan.is_rollback = is_rollback;
+  feeder_action->set_obj(install_plan);
+  auto runner_action = std::make_unique<PostinstallRunnerAction>(
+      &fake_boot_control_, &fake_hardware_);
+  postinstall_action_ = runner_action.get();
+  runner_action->set_delegate(setup_action_delegate_);
+  BondActions(feeder_action.get(), runner_action.get());
+  auto collector_action =
+      std::make_unique<ObjectCollectorAction<InstallPlan>>();
+  BondActions(runner_action.get(), collector_action.get());
+  processor.EnqueueAction(std::move(feeder_action));
+  processor.EnqueueAction(std::move(runner_action));
+  processor.EnqueueAction(std::move(collector_action));
   processor.set_delegate(&processor_delegate_);
 
   loop_.PostTask(
@@ -240,7 +247,8 @@
 // /postinst command which only exits 0.
 TEST_F(PostinstallRunnerActionTest, RunAsRootSimpleTest) {
   ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
-  RunPosinstallAction(loop.dev(), kPostinstallDefaultScript, false);
+
+  RunPostinstallAction(loop.dev(), kPostinstallDefaultScript, false, false);
   EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
   EXPECT_TRUE(processor_delegate_.processing_done_called_);
 
@@ -250,14 +258,31 @@
 
 TEST_F(PostinstallRunnerActionTest, RunAsRootRunSymlinkFileTest) {
   ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
-  RunPosinstallAction(loop.dev(), "bin/postinst_link", false);
+  RunPostinstallAction(loop.dev(), "bin/postinst_link", false, false);
   EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
 }
 
 TEST_F(PostinstallRunnerActionTest, RunAsRootPowerwashRequiredTest) {
   ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
   // Run a simple postinstall program but requiring a powerwash.
-  RunPosinstallAction(loop.dev(), "bin/postinst_example", true);
+  RunPostinstallAction(loop.dev(),
+                       "bin/postinst_example",
+                       /*powerwash_required=*/true,
+                       false);
+  EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
+
+  // Check that powerwash was scheduled.
+  EXPECT_TRUE(fake_hardware_.IsPowerwashScheduled());
+}
+
+TEST_F(PostinstallRunnerActionTest, RunAsRootRollbackTest) {
+  ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
+
+  // Run a simple postinstall program, rollback happened.
+  RunPostinstallAction(loop.dev(),
+                       "bin/postinst_example",
+                       false,
+                       /*is_rollback=*/true);
   EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
 
   // Check that powerwash was scheduled.
@@ -267,7 +292,7 @@
 // Runs postinstall from a partition file that doesn't mount, so it should
 // fail.
 TEST_F(PostinstallRunnerActionTest, RunAsRootCantMountTest) {
-  RunPosinstallAction("/dev/null", kPostinstallDefaultScript, false);
+  RunPostinstallAction("/dev/null", kPostinstallDefaultScript, false, false);
   EXPECT_EQ(ErrorCode::kPostinstallRunnerError, processor_delegate_.code_);
 
   // In case of failure, Postinstall should not signal a powerwash even if it
@@ -279,7 +304,7 @@
 // fail.
 TEST_F(PostinstallRunnerActionTest, RunAsRootErrScriptTest) {
   ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
-  RunPosinstallAction(loop.dev(), "bin/postinst_fail1", false);
+  RunPostinstallAction(loop.dev(), "bin/postinst_fail1", false, false);
   EXPECT_EQ(ErrorCode::kPostinstallRunnerError, processor_delegate_.code_);
 }
 
@@ -287,7 +312,7 @@
 // UMA with a different error code. Test those cases are properly detected.
 TEST_F(PostinstallRunnerActionTest, RunAsRootFirmwareBErrScriptTest) {
   ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
-  RunPosinstallAction(loop.dev(), "bin/postinst_fail3", false);
+  RunPostinstallAction(loop.dev(), "bin/postinst_fail3", false, false);
   EXPECT_EQ(ErrorCode::kPostinstallBootedFromFirmwareB,
             processor_delegate_.code_);
 }
@@ -295,7 +320,7 @@
 // Check that you can't specify an absolute path.
 TEST_F(PostinstallRunnerActionTest, RunAsRootAbsolutePathNotAllowedTest) {
   ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
-  RunPosinstallAction(loop.dev(), "/etc/../bin/sh", false);
+  RunPostinstallAction(loop.dev(), "/etc/../bin/sh", false, false);
   EXPECT_EQ(ErrorCode::kPostinstallRunnerError, processor_delegate_.code_);
 }
 
@@ -304,7 +329,7 @@
 // SElinux labels are only set on Android.
 TEST_F(PostinstallRunnerActionTest, RunAsRootCheckFileContextsTest) {
   ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
-  RunPosinstallAction(loop.dev(), "bin/self_check_context", false);
+  RunPostinstallAction(loop.dev(), "bin/self_check_context", false, false);
   EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
 }
 #endif  // __ANDROID__
@@ -317,7 +342,7 @@
   loop_.PostTask(FROM_HERE,
                  base::Bind(&PostinstallRunnerActionTest::SuspendRunningAction,
                             base::Unretained(this)));
-  RunPosinstallAction(loop.dev(), "bin/postinst_suspend", false);
+  RunPostinstallAction(loop.dev(), "bin/postinst_suspend", false, false);
   // postinst_suspend returns 0 only if it was suspended at some point.
   EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
   EXPECT_TRUE(processor_delegate_.processing_done_called_);
@@ -329,7 +354,7 @@
 
   // Wait for the action to start and then cancel it.
   CancelWhenStarted();
-  RunPosinstallAction(loop.dev(), "bin/postinst_suspend", false);
+  RunPostinstallAction(loop.dev(), "bin/postinst_suspend", false, false);
   // When canceling the action, the action never finished and therefore we had
   // a ProcessingStopped call instead.
   EXPECT_FALSE(processor_delegate_.code_set_);
@@ -352,7 +377,7 @@
 
   ScopedLoopbackDeviceBinder loop(postinstall_image_, false, nullptr);
   setup_action_delegate_ = &mock_delegate_;
-  RunPosinstallAction(loop.dev(), "bin/postinst_progress", false);
+  RunPostinstallAction(loop.dev(), "bin/postinst_progress", false, false);
   EXPECT_EQ(ErrorCode::kSuccess, processor_delegate_.code_);
 }
 
diff --git a/payload_generator/deflate_utils.cc b/payload_generator/deflate_utils.cc
index e331142..2719048 100644
--- a/payload_generator/deflate_utils.cc
+++ b/payload_generator/deflate_utils.cc
@@ -287,12 +287,16 @@
     }
 
     // Search for deflates if the file is in zip format.
+    // .zvoice files may eventually move out of rootfs. If that happens, remove
+    // ".zvoice" (crbug.com/782918).
+    const string zip_file_extensions[] = {".apk", ".zip", ".jar", ".zvoice"};
     bool is_zip =
-        base::EndsWith(
-            file.name, ".apk", base::CompareCase::INSENSITIVE_ASCII) ||
-        base::EndsWith(
-            file.name, ".zip", base::CompareCase::INSENSITIVE_ASCII) ||
-        base::EndsWith(file.name, ".jar", base::CompareCase::INSENSITIVE_ASCII);
+        any_of(zip_file_extensions,
+               std::end(zip_file_extensions),
+               [&file](const string& ext) {
+                 return base::EndsWith(
+                     file.name, ext, base::CompareCase::INSENSITIVE_ASCII);
+               });
 
     if (is_zip && extract_deflates) {
       brillo::Blob data;
diff --git a/payload_generator/generate_delta_main.cc b/payload_generator/generate_delta_main.cc
index 1f86313..b842f33 100644
--- a/payload_generator/generate_delta_main.cc
+++ b/payload_generator/generate_delta_main.cc
@@ -150,13 +150,16 @@
   LOG_IF(FATAL, out_file.empty())
       << "Must pass --out_file to sign payload.";
   LOG_IF(FATAL, payload_signature_file.empty())
-      << "Must pass --signature_file to sign payload.";
-  vector<brillo::Blob> signatures, metadata_signatures;
-  SignatureFileFlagToBlobs(payload_signature_file, &signatures);
+      << "Must pass --payload_signature_file to sign payload.";
+  vector<brillo::Blob> payload_signatures, metadata_signatures;
+  SignatureFileFlagToBlobs(payload_signature_file, &payload_signatures);
   SignatureFileFlagToBlobs(metadata_signature_file, &metadata_signatures);
   uint64_t final_metadata_size;
-  CHECK(PayloadSigner::AddSignatureToPayload(in_file, signatures,
-      metadata_signatures, out_file, &final_metadata_size));
+  CHECK(PayloadSigner::AddSignatureToPayload(in_file,
+                                             payload_signatures,
+                                             metadata_signatures,
+                                             out_file,
+                                             &final_metadata_size));
   LOG(INFO) << "Done signing payload. Final metadata size = "
             << final_metadata_size;
   if (!out_metadata_size_file.empty()) {
@@ -287,8 +290,8 @@
                 "Path to the .map files associated with the partition files "
                 "in the new partition, similar to the -old_mapfiles flag.");
   DEFINE_string(partition_names,
-                string(kLegacyPartitionNameRoot) + ":" +
-                kLegacyPartitionNameKernel,
+                string(kPartitionNameRoot) + ":" +
+                kPartitionNameKernel,
                 "Names of the partitions. To pass multiple names, use a single "
                 "argument with a colon between names, e.g. "
                 "name:name2:name3:last_name . Name can not be empty, and it "
@@ -311,7 +314,8 @@
                 "You may pass in multiple sizes by colon separating them. E.g. "
                 "2048:2048:4096 will assume 3 signatures, the first two with "
                 "2048 size and the last 4096.");
-  DEFINE_string(signature_file, "",
+  DEFINE_string(payload_signature_file,
+                "",
                 "Raw signature file to sign payload with. To pass multiple "
                 "signatures, use a single argument with a colon between paths, "
                 "e.g. /path/to/sig:/path/to/next:/path/to/last_sig . Each "
@@ -406,9 +410,12 @@
                             FLAGS_out_metadata_hash_file, FLAGS_in_file);
     return 0;
   }
-  if (!FLAGS_signature_file.empty()) {
-    SignPayload(FLAGS_in_file, FLAGS_out_file, FLAGS_signature_file,
-                FLAGS_metadata_signature_file, FLAGS_out_metadata_size_file);
+  if (!FLAGS_payload_signature_file.empty()) {
+    SignPayload(FLAGS_in_file,
+                FLAGS_out_file,
+                FLAGS_payload_signature_file,
+                FLAGS_metadata_signature_file,
+                FLAGS_out_metadata_size_file);
     return 0;
   }
   if (!FLAGS_public_key.empty()) {
@@ -444,8 +451,8 @@
     LOG_IF(FATAL, partition_names.size() != 2)
         << "To support more than 2 partitions, please use the "
         << "--new_partitions flag and major version 2.";
-    LOG_IF(FATAL, partition_names[0] != kLegacyPartitionNameRoot ||
-                  partition_names[1] != kLegacyPartitionNameKernel)
+    LOG_IF(FATAL, partition_names[0] != kPartitionNameRoot ||
+                  partition_names[1] != kPartitionNameKernel)
         << "To support non-default partition name, please use the "
         << "--new_partitions flag and major version 2.";
   }
diff --git a/payload_generator/inplace_generator.cc b/payload_generator/inplace_generator.cc
index febdcce..62608e5 100644
--- a/payload_generator/inplace_generator.cc
+++ b/payload_generator/inplace_generator.cc
@@ -802,7 +802,7 @@
                                config.hard_chunk_size / config.block_size);
   size_t soft_chunk_blocks = config.soft_chunk_size / config.block_size;
   uint64_t partition_size = new_part.size;
-  if (new_part.name == kLegacyPartitionNameRoot)
+  if (new_part.name == kPartitionNameRoot)
     partition_size = config.rootfs_partition_size;
 
   LOG(INFO) << "Delta compressing " << new_part.name << " partition...";
diff --git a/payload_generator/payload_file.cc b/payload_generator/payload_file.cc
index f48d2a2..d5c59ce 100644
--- a/payload_generator/payload_file.cc
+++ b/payload_generator/payload_file.cc
@@ -82,8 +82,8 @@
                                const vector<AnnotatedOperation>& aops) {
   // Check partitions order for Chrome OS
   if (major_version_ == kChromeOSMajorPayloadVersion) {
-    const vector<const char*> part_order = { kLegacyPartitionNameRoot,
-                                             kLegacyPartitionNameKernel };
+    const vector<const char*> part_order = { kPartitionNameRoot,
+                                             kPartitionNameKernel };
     TEST_AND_RETURN_FALSE(part_vec_.size() < part_order.size());
     TEST_AND_RETURN_FALSE(new_conf.name == part_order[part_vec_.size()]);
   }
@@ -153,7 +153,7 @@
         *(partition->mutable_new_partition_info()) = part.new_info;
     } else {
       // major_version_ == kChromeOSMajorPayloadVersion
-      if (part.name == kLegacyPartitionNameKernel) {
+      if (part.name == kPartitionNameKernel) {
         for (const AnnotatedOperation& aop : part.aops)
           *manifest_.add_kernel_install_operations() = aop.op;
         if (part.old_info.has_size() || part.old_info.has_hash())
diff --git a/payload_generator/payload_generation_config.cc b/payload_generator/payload_generation_config.cc
index 212d45f..5fe56b5 100644
--- a/payload_generator/payload_generation_config.cc
+++ b/payload_generator/payload_generation_config.cc
@@ -216,7 +216,7 @@
     TEST_AND_RETURN_FALSE(part.ValidateExists());
     TEST_AND_RETURN_FALSE(part.size % block_size == 0);
     if (version.minor == kInPlaceMinorPayloadVersion &&
-        part.name == kLegacyPartitionNameRoot)
+        part.name == kPartitionNameRoot)
       TEST_AND_RETURN_FALSE(rootfs_partition_size >= part.size);
     if (version.major == kChromeOSMajorPayloadVersion)
       TEST_AND_RETURN_FALSE(part.postinstall.IsEmpty());
diff --git a/payload_state.cc b/payload_state.cc
index d98ffe1..c4eb950 100644
--- a/payload_state.cc
+++ b/payload_state.cc
@@ -64,6 +64,7 @@
       url_index_(0),
       url_failure_count_(0),
       url_switch_count_(0),
+      rollback_happened_(false),
       attempt_num_bytes_downloaded_(0),
       attempt_connection_type_(metrics::ConnectionType::kUnknown),
       attempt_type_(AttemptType::kUpdate) {
@@ -93,6 +94,7 @@
   }
   LoadNumReboots();
   LoadNumResponsesSeen();
+  LoadRollbackHappened();
   LoadRollbackVersion();
   LoadP2PFirstAttemptTimestamp();
   LoadP2PNumAttempts();
@@ -355,8 +357,11 @@
     case ErrorCode::kOmahaRequestXMLHasEntityDecl:
     case ErrorCode::kFilesystemVerifierError:
     case ErrorCode::kUserCanceled:
+    case ErrorCode::kOmahaUpdateIgnoredOverCellular:
     case ErrorCode::kUpdatedButNotActive:
     case ErrorCode::kNoUpdate:
+    case ErrorCode::kRollbackNotPossible:
+    case ErrorCode::kFirstActiveOmahaPingSentPersistenceError:
       LOG(INFO) << "Not incrementing URL index or failure count for this error";
       break;
 
@@ -406,9 +411,11 @@
     }
   }
 
-  if (!system_state_->hardware()->IsOfficialBuild()) {
+  if (!system_state_->hardware()->IsOfficialBuild() &&
+      !prefs_->Exists(kPrefsNoIgnoreBackoff)) {
     // Backoffs are needed only for official builds. We do not want any delays
-    // or update failures due to backoffs during testing or development.
+    // or update failures due to backoffs during testing or development. Unless
+    // the |kPrefsNoIgnoreBackoff| is manually set.
     LOG(INFO) << "No backoffs for test/dev images. "
               << "Can proceed with the download";
     return false;
@@ -787,6 +794,7 @@
   SetP2PNumAttempts(0);
   SetP2PFirstAttemptTimestamp(Time());  // Set to null time
   SetScatteringWaitPeriod(TimeDelta());
+  SetStagingWaitPeriod(TimeDelta());
 }
 
 void PayloadState::ResetRollbackVersion() {
@@ -909,7 +917,7 @@
 
 void PayloadState::LoadScatteringWaitPeriod() {
   SetScatteringWaitPeriod(TimeDelta::FromSeconds(
-      GetPersistedValue(kPrefsWallClockWaitPeriod, prefs_)));
+      GetPersistedValue(kPrefsWallClockScatteringWaitPeriod, prefs_)));
 }
 
 void PayloadState::SetScatteringWaitPeriod(TimeDelta wait_period) {
@@ -918,10 +926,27 @@
   LOG(INFO) << "Scattering Wait Period (seconds) = "
             << scattering_wait_period_.InSeconds();
   if (scattering_wait_period_.InSeconds() > 0) {
-    prefs_->SetInt64(kPrefsWallClockWaitPeriod,
+    prefs_->SetInt64(kPrefsWallClockScatteringWaitPeriod,
                      scattering_wait_period_.InSeconds());
   } else {
-    prefs_->Delete(kPrefsWallClockWaitPeriod);
+    prefs_->Delete(kPrefsWallClockScatteringWaitPeriod);
+  }
+}
+
+void PayloadState::LoadStagingWaitPeriod() {
+  SetStagingWaitPeriod(TimeDelta::FromSeconds(
+      GetPersistedValue(kPrefsWallClockStagingWaitPeriod, prefs_)));
+}
+
+void PayloadState::SetStagingWaitPeriod(TimeDelta wait_period) {
+  CHECK(prefs_);
+  staging_wait_period_ = wait_period;
+  LOG(INFO) << "Staging Wait Period (days) =" << staging_wait_period_.InDays();
+  if (staging_wait_period_.InSeconds() > 0) {
+    prefs_->SetInt64(kPrefsWallClockStagingWaitPeriod,
+                     staging_wait_period_.InSeconds());
+  } else {
+    prefs_->Delete(kPrefsWallClockStagingWaitPeriod);
   }
 }
 
@@ -1070,6 +1095,25 @@
   SetNumReboots(GetPersistedValue(kPrefsNumReboots, prefs_));
 }
 
+void PayloadState::LoadRollbackHappened() {
+  CHECK(powerwash_safe_prefs_);
+  bool rollback_happened = false;
+  powerwash_safe_prefs_->GetBoolean(kPrefsRollbackHappened, &rollback_happened);
+  SetRollbackHappened(rollback_happened);
+}
+
+void PayloadState::SetRollbackHappened(bool rollback_happened) {
+  CHECK(powerwash_safe_prefs_);
+  LOG(INFO) << "Setting rollback-happened to " << rollback_happened << ".";
+  rollback_happened_ = rollback_happened;
+  if (rollback_happened) {
+    powerwash_safe_prefs_->SetBoolean(kPrefsRollbackHappened,
+                                      rollback_happened);
+  } else {
+    powerwash_safe_prefs_->Delete(kPrefsRollbackHappened);
+  }
+}
+
 void PayloadState::LoadRollbackVersion() {
   CHECK(powerwash_safe_prefs_);
   string rollback_version;
diff --git a/payload_state.h b/payload_state.h
index 17c1ce0..cb6454f 100644
--- a/payload_state.h
+++ b/payload_state.h
@@ -80,7 +80,8 @@
   }
 
   inline std::string GetCurrentUrl() override {
-    return candidate_urls_.size() && candidate_urls_[payload_index_].size()
+    return (payload_index_ < candidate_urls_.size() &&
+            url_index_ < candidate_urls_[payload_index_].size())
                ? candidate_urls_[payload_index_][url_index_]
                : "";
   }
@@ -119,6 +120,10 @@
 
   void UpdateEngineStarted() override;
 
+  inline bool GetRollbackHappened() override { return rollback_happened_; }
+
+  void SetRollbackHappened(bool rollback_happened) override;
+
   inline std::string GetRollbackVersion() override {
     return rollback_version_;
   }
@@ -142,6 +147,8 @@
 
   void SetScatteringWaitPeriod(base::TimeDelta wait_period) override;
 
+  void SetStagingWaitPeriod(base::TimeDelta wait_period) override;
+
   void SetP2PUrl(const std::string& url) override {
     p2p_url_ = url;
   }
@@ -162,6 +169,7 @@
   FRIEND_TEST(PayloadStateTest, RebootAfterUpdateFailedMetric);
   FRIEND_TEST(PayloadStateTest, RebootAfterUpdateSucceed);
   FRIEND_TEST(PayloadStateTest, RebootAfterCanceledUpdate);
+  FRIEND_TEST(PayloadStateTest, RollbackHappened);
   FRIEND_TEST(PayloadStateTest, RollbackVersion);
   FRIEND_TEST(PayloadStateTest, UpdateSuccessWithWipedPrefs);
 
@@ -359,6 +367,10 @@
                                uint64_t total_bytes_downloaded,
                                bool log);
 
+  // Loads whether rollback has happened on this device since the last update
+  // check where policy was available. This info is preserved over powerwash.
+  void LoadRollbackHappened();
+
   // Loads the blacklisted version from our prefs file.
   void LoadRollbackVersion();
 
@@ -370,9 +382,10 @@
   void ResetRollbackVersion();
 
   inline uint32_t GetUrlIndex() {
-    return url_index_ ? std::min(candidate_urls_[payload_index_].size() - 1,
-                                 url_index_)
-                      : 0;
+    return (url_index_ != 0 && payload_index_ < candidate_urls_.size())
+               ? std::min(candidate_urls_[payload_index_].size() - 1,
+                          url_index_)
+               : 0;
   }
 
   // Computes the list of candidate URLs from the total list of payload URLs in
@@ -397,8 +410,6 @@
   // increments num_reboots.
   void UpdateNumReboots();
 
-
-
   // Loads the |kPrefsP2PFirstAttemptTimestamp| state variable from disk
   // into |p2p_first_attempt_timestamp_|.
   void LoadP2PFirstAttemptTimestamp();
@@ -415,6 +426,9 @@
   // Loads the persisted scattering wallclock-based wait period.
   void LoadScatteringWaitPeriod();
 
+  // Loads the persisted staging wallclock-based wait period.
+  void LoadStagingWaitPeriod();
+
   // Get the total size of all payloads.
   int64_t GetPayloadSize();
 
@@ -546,6 +560,11 @@
   // allowed as per device policy.
   std::vector<std::vector<std::string>> candidate_urls_;
 
+  // This stores whether rollback has happened since the last time device policy
+  // was available during update check. When this is set, we're preventing
+  // forced updates to avoid update-rollback loops.
+  bool rollback_happened_;
+
   // This stores a blacklisted version set as part of rollback. When we rollback
   // we store the version of the os from which we are rolling back from in order
   // to guarantee that we do not re-update to it on the next au attempt after
@@ -570,6 +589,9 @@
   // The current scattering wallclock-based wait period.
   base::TimeDelta scattering_wait_period_;
 
+  // The current staging wallclock-based wait period.
+  base::TimeDelta staging_wait_period_;
+
   DISALLOW_COPY_AND_ASSIGN(PayloadState);
 };
 
diff --git a/payload_state_interface.h b/payload_state_interface.h
index f553909..d384a0e 100644
--- a/payload_state_interface.h
+++ b/payload_state_interface.h
@@ -155,6 +155,16 @@
   // Called at update_engine startup to do various house-keeping.
   virtual void UpdateEngineStarted() = 0;
 
+  // Returns whether a rollback happened since the last update check with policy
+  // present.
+  virtual bool GetRollbackHappened() = 0;
+
+  // Sets whether rollback has happened on this device since the last update
+  // check where policy was available. This info is preserved over powerwash.
+  // This prevents forced updates happening on a rolled back device before
+  // device policy is available.
+  virtual void SetRollbackHappened(bool rollback_happened) = 0;
+
   // Returns the version from before a rollback if our last update was a
   // rollback.
   virtual std::string GetRollbackVersion() = 0;
@@ -195,6 +205,9 @@
 
   // Switch to next payload.
   virtual bool NextPayload() = 0;
+
+  // Sets and persists the staging wallclock-based wait period.
+  virtual void SetStagingWaitPeriod(base::TimeDelta wait_period) = 0;
 };
 
 }  // namespace chromeos_update_engine
diff --git a/payload_state_unittest.cc b/payload_state_unittest.cc
index 52c28d0..637891b 100644
--- a/payload_state_unittest.cc
+++ b/payload_state_unittest.cc
@@ -985,6 +985,37 @@
   EXPECT_EQ(0U, payload_state.GetNumReboots());
 }
 
+TEST(PayloadStateTest, RollbackHappened) {
+  FakeSystemState fake_system_state;
+  PayloadState payload_state;
+
+  NiceMock<MockPrefs>* mock_powerwash_safe_prefs =
+      fake_system_state.mock_powerwash_safe_prefs();
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+  // Verify pre-conditions are good.
+  EXPECT_FALSE(payload_state.GetRollbackHappened());
+
+  // Set to true.
+  EXPECT_CALL(*mock_powerwash_safe_prefs,
+              SetBoolean(kPrefsRollbackHappened, true));
+  payload_state.SetRollbackHappened(true);
+  EXPECT_TRUE(payload_state.GetRollbackHappened());
+
+  // Set to false.
+  EXPECT_CALL(*mock_powerwash_safe_prefs, Delete(kPrefsRollbackHappened));
+  payload_state.SetRollbackHappened(false);
+  EXPECT_FALSE(payload_state.GetRollbackHappened());
+
+  // Let's verify we can reload it correctly.
+  EXPECT_CALL(*mock_powerwash_safe_prefs, GetBoolean(kPrefsRollbackHappened, _))
+      .WillOnce(DoAll(SetArgPointee<1>(true), Return(true)));
+  EXPECT_CALL(*mock_powerwash_safe_prefs,
+              SetBoolean(kPrefsRollbackHappened, true));
+  payload_state.LoadRollbackHappened();
+  EXPECT_TRUE(payload_state.GetRollbackHappened());
+}
+
 TEST(PayloadStateTest, RollbackVersion) {
   FakeSystemState fake_system_state;
   PayloadState payload_state;
diff --git a/real_system_state.cc b/real_system_state.cc
index 0ce326b..9dab3a1 100644
--- a/real_system_state.cc
+++ b/real_system_state.cc
@@ -18,6 +18,7 @@
 
 #include <memory>
 #include <string>
+#include <utility>
 
 #include <base/bind.h>
 #include <base/files/file_util.h>
@@ -34,6 +35,7 @@
 #include "update_engine/common/hardware.h"
 #include "update_engine/common/utils.h"
 #include "update_engine/metrics_reporter_omaha.h"
+#include "update_engine/update_boot_flags_action.h"
 #if USE_DBUS
 #include "update_engine/dbus_connection.h"
 #endif  // USE_DBUS
@@ -67,8 +69,8 @@
   }
 
 #if USE_CHROME_KIOSK_APP
-  libcros_proxy_.reset(new org::chromium::LibCrosServiceInterfaceProxy(
-      DBusConnection::Get()->GetDBus(), chromeos::kLibCrosServiceName));
+  kiosk_app_proxy_.reset(new org::chromium::KioskAppServiceInterfaceProxy(
+      DBusConnection::Get()->GetDBus(), chromeos::kKioskAppServiceName));
 #endif  // USE_CHROME_KIOSK_APP
 
   LOG_IF(INFO, !hardware_->IsNormalBootMode()) << "Booted in dev mode.";
@@ -150,7 +152,7 @@
   chromeos_update_manager::State* um_state =
       chromeos_update_manager::DefaultStateFactory(&policy_provider_,
 #if USE_CHROME_KIOSK_APP
-                                                   libcros_proxy_.get(),
+                                                   kiosk_app_proxy_.get(),
 #else
                                                    nullptr,
 #endif  // USE_CHROME_KIOSK_APP
@@ -183,11 +185,14 @@
   // Initiate update checks.
   update_attempter_->ScheduleUpdates();
 
+  auto update_boot_flags_action =
+      std::make_unique<UpdateBootFlagsAction>(boot_control_.get());
+  processor_.EnqueueAction(std::move(update_boot_flags_action));
   // Update boot flags after 45 seconds.
   MessageLoop::current()->PostDelayedTask(
       FROM_HERE,
-      base::Bind(&UpdateAttempter::UpdateBootFlags,
-                 base::Unretained(update_attempter_.get())),
+      base::Bind(&ActionProcessor::StartProcessing,
+                 base::Unretained(&processor_)),
       base::TimeDelta::FromSeconds(45));
 
   // Broadcast the update engine status on startup to ensure consistent system
diff --git a/real_system_state.h b/real_system_state.h
index 49f7c31..5239160 100644
--- a/real_system_state.h
+++ b/real_system_state.h
@@ -25,7 +25,7 @@
 #include <policy/device_policy.h>
 
 #if USE_CHROME_KIOSK_APP
-#include <libcros/dbus-proxies.h>
+#include <kiosk-app/dbus-proxies.h>
 #endif  // USE_CHROME_KIOSK_APP
 
 #include "update_engine/certificate_checker.h"
@@ -129,7 +129,8 @@
  private:
   // Real DBus proxies using the DBus connection.
 #if USE_CHROME_KIOSK_APP
-  std::unique_ptr<org::chromium::LibCrosServiceInterfaceProxy> libcros_proxy_;
+  std::unique_ptr<org::chromium::KioskAppServiceInterfaceProxy>
+      kiosk_app_proxy_;
 #endif  // USE_CHROME_KIOSK_APP
 
   // Interface for the power manager.
@@ -184,6 +185,8 @@
   // rebooted. Important for tracking whether you are running instance of the
   // update engine on first boot or due to a crash/restart.
   bool system_rebooted_{false};
+
+  ActionProcessor processor_;
 };
 
 }  // namespace chromeos_update_engine
diff --git a/scripts/brillo_update_payload b/scripts/brillo_update_payload
index e1e9c27..531a1bd 100755
--- a/scripts/brillo_update_payload
+++ b/scripts/brillo_update_payload
@@ -25,6 +25,7 @@
 #  sign        generate a signed payload
 #  properties  generate a properties file from a payload
 #  verify      verify a payload by recreating a target image.
+#  check       verify a payload using paycheck (static testing)
 #
 #  Generate command arguments:
 #  --payload             generated unsigned payload output file
@@ -67,6 +68,9 @@
 #  --payload             payload input file
 #  --source_image        verify payload to the specified source image.
 #  --target_image        the target image to verify upon.
+#
+#  Check command arguments:
+#     Symmetrical with the verify command.
 
 
 # Exit codes:
@@ -82,7 +86,7 @@
 }
 
 # Loads shflags. We first look at the default install location; then look for
-# crosutils (chroot); finally check our own directory (au-generator zipfile).
+# crosutils (chroot); finally check our own directory.
 load_shflags() {
   local my_dir="$(dirname "$(readlink -f "$0")")"
   local path
@@ -102,7 +106,9 @@
 for signing."
 HELP_SIGN="sign: Insert the signatures into the unsigned payload."
 HELP_PROPERTIES="properties: Extract payload properties to a file."
-HELP_VERIFY="verify: Verify a (signed) update payload."
+HELP_VERIFY="verify: Verify a (signed) update payload using delta_generator."
+HELP_CHECK="check: Check a (signed) update payload using paycheck (static \
+testing)."
 
 usage() {
   echo "Supported commands:"
@@ -112,13 +118,14 @@
   echo "${HELP_SIGN}"
   echo "${HELP_PROPERTIES}"
   echo "${HELP_VERIFY}"
+  echo "${HELP_CHECK}"
   echo
   echo "Use: \"$0 <command> --help\" for more options."
 }
 
 # Check that a command is specified.
 if [[ $# -lt 1 ]]; then
-  echo "Please specify a command [generate|hash|sign|properties]"
+  echo "Please specify a command [generate|hash|sign|properties|verify|check]"
   exit 1
 fi
 
@@ -147,6 +154,10 @@
     FLAGS_HELP="${HELP_VERIFY}"
     ;;
 
+  check)
+    FLAGS_HELP="${HELP_CHECK}"
+    ;;
+
   *)
     echo "Unrecognized command: \"${COMMAND}\"" >&2
     usage >&2
@@ -202,7 +213,7 @@
     "Path to output the extracted property files. If '-' is passed stdout will \
 be used."
 fi
-if [[ "${COMMAND}" == "verify" ]]; then
+if [[ "${COMMAND}" == "verify" || "${COMMAND}" == "check" ]]; then
   DEFINE_string payload "" \
     "Path to the input payload file."
   DEFINE_string target_image "" \
@@ -366,18 +377,15 @@
   # updater supports a newer major version.
   FORCE_MAJOR_VERSION="1"
 
-  # When generating legacy Chrome OS images, we need to use "boot" and "system"
-  # for the partition names to be compatible with updating Brillo devices with
-  # Chrome OS images.
-  eval ${partitions_array}[boot]=\""${kernel}"\"
-  eval ${partitions_array}[system]=\""${root}"\"
+  eval ${partitions_array}[kernel]=\""${kernel}"\"
+  eval ${partitions_array}[root]=\""${root}"\"
 
   if [[ -n "${partitions_order}" ]]; then
-    eval "${partitions_order}=( \"system\" \"boot\" )"
+    eval "${partitions_order}=( \"root\" \"kernel\" )"
   fi
 
   local part varname
-  for part in boot system; do
+  for part in kernel root; do
     varname="${partitions_array}[${part}]"
     printf "md5sum of %s: " "${varname}"
     md5sum "${!varname}"
@@ -514,6 +522,24 @@
   done
 }
 
+extract_payload_images() {
+  local payload_type=$1
+  echo "Extracting images for ${payload_type} update."
+
+  if [[ "${payload_type}" == "delta" ]]; then
+    extract_image "${FLAGS_source_image}" SRC_PARTITIONS
+  fi
+  extract_image "${FLAGS_target_image}" DST_PARTITIONS PARTITIONS_ORDER
+}
+
+get_payload_type() {
+  if [[ -z "${FLAGS_source_image}" ]]; then
+    echo "full"
+  else
+    echo "delta"
+  fi
+}
+
 validate_generate() {
   [[ -n "${FLAGS_payload}" ]] ||
     die "You must specify an output filename with --payload FILENAME"
@@ -537,7 +563,7 @@
 
   echo "Generating ${payload_type} update."
   # Common payload args:
-  GENERATOR_ARGS=( -out_file="${FLAGS_payload}" )
+  GENERATOR_ARGS=( --out_file="${FLAGS_payload}" )
 
   local part old_partitions="" new_partitions="" partition_names=""
   local old_mapfiles="" new_mapfiles=""
@@ -558,16 +584,16 @@
 
   # Target image args:
   GENERATOR_ARGS+=(
-    -partition_names="${partition_names}"
-    -new_partitions="${new_partitions}"
-    -new_mapfiles="${new_mapfiles}"
+    --partition_names="${partition_names}"
+    --new_partitions="${new_partitions}"
+    --new_mapfiles="${new_mapfiles}"
   )
 
   if [[ "${payload_type}" == "delta" ]]; then
     # Source image args:
     GENERATOR_ARGS+=(
-      -old_partitions="${old_partitions}"
-      -old_mapfiles="${old_mapfiles}"
+      --old_partitions="${old_partitions}"
+      --old_mapfiles="${old_mapfiles}"
     )
     if [[ -n "${FORCE_MINOR_VERSION}" ]]; then
       GENERATOR_ARGS+=( --minor_version="${FORCE_MINOR_VERSION}" )
@@ -615,10 +641,10 @@
 
 cmd_hash() {
   "${GENERATOR}" \
-      -in_file="${FLAGS_unsigned_payload}" \
-      -signature_size="${FLAGS_signature_size}" \
-      -out_hash_file="${FLAGS_payload_hash_file}" \
-      -out_metadata_hash_file="${FLAGS_metadata_hash_file}"
+      --in_file="${FLAGS_unsigned_payload}" \
+      --signature_size="${FLAGS_signature_size}" \
+      --out_hash_file="${FLAGS_payload_hash_file}" \
+      --out_metadata_hash_file="${FLAGS_metadata_hash_file}"
 
   echo "Done generating hash."
 }
@@ -645,11 +671,11 @@
 
 cmd_sign() {
   GENERATOR_ARGS=(
-    -in_file="${FLAGS_unsigned_payload}"
-    -signature_size="${FLAGS_signature_size}"
-    -signature_file="${FLAGS_payload_signature_file}"
-    -metadata_signature_file="${FLAGS_metadata_signature_file}"
-    -out_file="${FLAGS_payload}"
+    --in_file="${FLAGS_unsigned_payload}"
+    --signature_size="${FLAGS_signature_size}"
+    --payload_signature_file="${FLAGS_payload_signature_file}"
+    --metadata_signature_file="${FLAGS_metadata_signature_file}"
+    --out_file="${FLAGS_payload}"
   )
 
   if [[ -n "${FLAGS_metadata_size_file}" ]]; then
@@ -670,11 +696,11 @@
 
 cmd_properties() {
   "${GENERATOR}" \
-      -in_file="${FLAGS_payload}" \
-      -properties_file="${FLAGS_properties_file}"
+      --in_file="${FLAGS_payload}" \
+      --properties_file="${FLAGS_properties_file}"
 }
 
-validate_verify() {
+validate_verify_and_check() {
   [[ -n "${FLAGS_payload}" ]] ||
     die "Error: you must specify an input filename with --payload FILENAME"
 
@@ -683,17 +709,8 @@
 }
 
 cmd_verify() {
-  local payload_type="delta"
-  if [[ -z "${FLAGS_source_image}" ]]; then
-    payload_type="full"
-  fi
-
-  echo "Extracting images for ${payload_type} update."
-
-  if [[ "${payload_type}" == "delta" ]]; then
-    extract_image "${FLAGS_source_image}" SRC_PARTITIONS
-  fi
-  extract_image "${FLAGS_target_image}" DST_PARTITIONS PARTITIONS_ORDER
+  local payload_type=$(get_payload_type)
+  extract_payload_images ${payload_type}
 
   declare -A TMP_PARTITIONS
   for part in "${PARTITIONS_ORDER[@]}"; do
@@ -708,7 +725,7 @@
 
   echo "Verifying ${payload_type} update."
   # Common payload args:
-  GENERATOR_ARGS=( -in_file="${FLAGS_payload}" )
+  GENERATOR_ARGS=( --in_file="${FLAGS_payload}" )
 
   local part old_partitions="" new_partitions="" partition_names=""
   for part in "${PARTITIONS_ORDER[@]}"; do
@@ -724,14 +741,14 @@
 
   # Target image args:
   GENERATOR_ARGS+=(
-    -partition_names="${partition_names}"
-    -new_partitions="${new_partitions}"
+    --partition_names="${partition_names}"
+    --new_partitions="${new_partitions}"
   )
 
   if [[ "${payload_type}" == "delta" ]]; then
     # Source image args:
     GENERATOR_ARGS+=(
-      -old_partitions="${old_partitions}"
+      --old_partitions="${old_partitions}"
     )
   fi
 
@@ -759,6 +776,33 @@
   fi
 }
 
+cmd_check() {
+  local payload_type=$(get_payload_type)
+  extract_payload_images ${payload_type}
+
+  local part dst_partitions="" src_partitions=""
+  for part in "${PARTITIONS_ORDER[@]}"; do
+    if [[ -n "${dst_partitions}" ]]; then
+      dst_partitions+=" "
+      src_partitions+=" "
+    fi
+    dst_partitions+="${DST_PARTITIONS[${part}]}"
+    src_partitions+="${SRC_PARTITIONS[${part}]:-}"
+  done
+
+  # Common payload args:
+  PAYCHECK_ARGS=( "${FLAGS_payload}" --type ${payload_type} \
+    --part_names ${PARTITIONS_ORDER[@]} \
+    --dst_part_paths ${dst_partitions} )
+
+  if [[ ! -z "${SRC_PARTITIONS[@]}" ]]; then
+    PAYCHECK_ARGS+=( --src_part_paths ${src_partitions} )
+  fi
+
+  echo "Checking ${payload_type} update."
+  check_update_payload ${PAYCHECK_ARGS[@]} --check
+}
+
 # Sanity check that the real generator exists:
 GENERATOR="$(which delta_generator || true)"
 [[ -x "${GENERATOR}" ]] || die "can't find delta_generator"
@@ -776,7 +820,10 @@
   properties) validate_properties
               cmd_properties
               ;;
-  verify) validate_verify
+  verify) validate_verify_and_check
           cmd_verify
           ;;
+  check) validate_verify_and_check
+         cmd_check
+         ;;
 esac
diff --git a/scripts/paycheck.py b/scripts/paycheck.py
index 96b1032..9d61778 100755
--- a/scripts/paycheck.py
+++ b/scripts/paycheck.py
@@ -26,6 +26,7 @@
 import sys
 import tempfile
 
+from update_payload import common
 from update_payload import error
 
 lib_dir = os.path.join(os.path.dirname(__file__), 'lib')
@@ -89,12 +90,18 @@
                                 'validation'))
   check_args.add_argument('-m', '--meta-sig', metavar='FILE',
                           help='verify metadata against its signature')
+  check_args.add_argument('-s', '--metadata-size', metavar='NUM', default=0,
+                          help='the metadata size to verify with the one in'
+                          ' payload')
+  # TODO(tbrindus): deprecated in favour of --part_sizes
   check_args.add_argument('-p', '--root-part-size', metavar='NUM',
                           default=0, type=int,
                           help='override rootfs partition size auto-inference')
   check_args.add_argument('-P', '--kern-part-size', metavar='NUM',
                           default=0, type=int,
                           help='override kernel partition size auto-inference')
+  check_args.add_argument('--part_sizes', metavar='NUM', nargs='+', type=int,
+                          help='override partition size auto-inference')
 
   apply_args = parser.add_argument_group('Applying payload')
   # TODO(ahassani): Extent extract-bsdiff to puffdiff too.
@@ -106,41 +113,66 @@
                           help='use the specified bspatch binary')
   apply_args.add_argument('--puffpatch-path', metavar='FILE',
                           help='use the specified puffpatch binary')
+  # TODO(tbrindus): deprecated in favour of --dst_part_paths
   apply_args.add_argument('--dst_kern', metavar='FILE',
                           help='destination kernel partition file')
   apply_args.add_argument('--dst_root', metavar='FILE',
                           help='destination root partition file')
+  # TODO(tbrindus): deprecated in favour of --src_part_paths
   apply_args.add_argument('--src_kern', metavar='FILE',
                           help='source kernel partition file')
   apply_args.add_argument('--src_root', metavar='FILE',
                           help='source root partition file')
+  # TODO(tbrindus): deprecated in favour of --out_dst_part_paths
   apply_args.add_argument('--out_dst_kern', metavar='FILE',
                           help='created destination kernel partition file')
   apply_args.add_argument('--out_dst_root', metavar='FILE',
                           help='created destination root partition file')
 
+  apply_args.add_argument('--src_part_paths', metavar='FILE', nargs='+',
+                          help='source partitition files')
+  apply_args.add_argument('--dst_part_paths', metavar='FILE', nargs='+',
+                          help='destination partition files')
+  apply_args.add_argument('--out_dst_part_paths', metavar='FILE', nargs='+',
+                          help='created destination partition files')
+
   parser.add_argument('payload', metavar='PAYLOAD', help='the payload file')
+  parser.add_argument('--part_names', metavar='NAME', nargs='+',
+                      help='names of partitions')
 
   # Parse command-line arguments.
   args = parser.parse_args(argv)
 
+  # TODO(tbrindus): temporary workaround to keep old-style flags from breaking
+  # without having to handle both types in our code. Remove after flag usage is
+  # removed from calling scripts.
+  args.part_names = args.part_names or [common.KERNEL, common.ROOTFS]
+  args.part_sizes = args.part_sizes or [args.kern_part_size,
+                                        args.root_part_size]
+  args.src_part_paths = args.src_part_paths or [args.src_kern, args.src_root]
+  args.dst_part_paths = args.dst_part_paths or [args.dst_kern, args.dst_root]
+  args.out_dst_part_paths = args.out_dst_part_paths or [args.out_dst_kern,
+                                                        args.out_dst_root]
+
+  # Make sure we don't have new dependencies on old flags by deleting them from
+  # the namespace here.
+  for old in ['kern_part_size', 'root_part_size', 'src_kern', 'src_root',
+              'dst_kern', 'dst_root', 'out_dst_kern', 'out_dst_root']:
+    delattr(args, old)
+
   # There are several options that imply --check.
   args.check = (args.check or args.report or args.assert_type or
                 args.block_size or args.allow_unhashed or
                 args.disabled_tests or args.meta_sig or args.key or
-                args.root_part_size or args.kern_part_size)
+                any(args.part_sizes) or args.metadata_size)
 
-  # Check the arguments, enforce payload type accordingly.
-  if (args.src_kern is None) != (args.src_root is None):
-    parser.error('--src_kern and --src_root should be given together')
-  if (args.dst_kern is None) != (args.dst_root is None):
-    parser.error('--dst_kern and --dst_root should be given together')
-  if (args.out_dst_kern is None) != (args.out_dst_root is None):
-    parser.error('--out_dst_kern and --out_dst_root should be given together')
+  for arg in ['part_sizes', 'src_part_paths', 'dst_part_paths',
+              'out_dst_part_paths']:
+    if len(args.part_names) != len(getattr(args, arg, [])):
+      parser.error('partitions in --%s do not match --part_names' % arg)
 
-  if (args.dst_kern and args.dst_root) or \
-     (args.out_dst_kern and args.out_dst_root):
-    if args.src_kern and args.src_root:
+  if all(args.dst_part_paths) or all(args.out_dst_part_paths):
+    if all(args.src_part_paths):
       if args.assert_type == _TYPE_FULL:
         parser.error('%s payload does not accept source partition arguments'
                      % _TYPE_FULL)
@@ -198,15 +230,16 @@
               report_file = open(args.report, 'w')
               do_close_report_file = True
 
+          part_sizes = dict(zip(args.part_names, args.part_sizes))
           metadata_sig_file = args.meta_sig and open(args.meta_sig)
           payload.Check(
               pubkey_file_name=args.key,
               metadata_sig_file=metadata_sig_file,
+              metadata_size=int(args.metadata_size),
               report_out_file=report_file,
               assert_type=args.assert_type,
               block_size=int(args.block_size),
-              rootfs_part_size=args.root_part_size,
-              kernel_part_size=args.kern_part_size,
+              part_sizes=part_sizes,
               allow_unhashed=args.allow_unhashed,
               disabled_tests=args.disabled_tests)
         finally:
@@ -216,46 +249,50 @@
             report_file.close()
 
       # Apply payload.
-      if (args.dst_root and args.dst_kern) or \
-         (args.out_dst_root and args.out_dst_kern):
+      if all(args.dst_part_paths) or all(args.out_dst_part_paths):
         dargs = {'bsdiff_in_place': not args.extract_bsdiff}
         if args.bspatch_path:
           dargs['bspatch_path'] = args.bspatch_path
         if args.puffpatch_path:
           dargs['puffpatch_path'] = args.puffpatch_path
         if args.assert_type == _TYPE_DELTA:
-          dargs['old_kernel_part'] = args.src_kern
-          dargs['old_rootfs_part'] = args.src_root
+          dargs['old_parts'] = dict(zip(args.part_names, args.src_part_paths))
 
-        if args.out_dst_kern and args.out_dst_root:
-          out_dst_kern = open(args.out_dst_kern, 'w+')
-          out_dst_root = open(args.out_dst_root, 'w+')
+        out_dst_parts = {}
+        file_handles = []
+        if all(args.out_dst_part_paths):
+          for name, path in zip(args.part_names, args.out_dst_part_paths):
+            handle = open(path, 'w+')
+            file_handles.append(handle)
+            out_dst_parts[name] = handle.name
         else:
-          out_dst_kern = tempfile.NamedTemporaryFile()
-          out_dst_root = tempfile.NamedTemporaryFile()
+          for name in args.part_names:
+            handle = tempfile.NamedTemporaryFile()
+            file_handles.append(handle)
+            out_dst_parts[name] = handle.name
 
-        payload.Apply(out_dst_kern.name, out_dst_root.name, **dargs)
+        payload.Apply(out_dst_parts, **dargs)
 
         # If destination kernel and rootfs partitions are not given, then this
         # just becomes an apply operation with no check.
-        if args.dst_kern and args.dst_root:
+        if all(args.dst_part_paths):
           # Prior to comparing, add the unused space past the filesystem
           # boundary in the new target partitions to become the same size as
           # the given partitions. This will truncate to larger size.
-          out_dst_kern.truncate(os.path.getsize(args.dst_kern))
-          out_dst_root.truncate(os.path.getsize(args.dst_root))
+          for part_name, out_dst_part, dst_part in zip(args.part_names,
+                                                       file_handles,
+                                                       args.dst_part_paths):
+            out_dst_part.truncate(os.path.getsize(dst_part))
 
-          # Compare resulting partitions with the ones from the target image.
-          if not filecmp.cmp(out_dst_kern.name, args.dst_kern):
-            raise error.PayloadError('Resulting kernel partition corrupted.')
-          if not filecmp.cmp(out_dst_root.name, args.dst_root):
-            raise error.PayloadError('Resulting rootfs partition corrupted.')
+            # Compare resulting partitions with the ones from the target image.
+            if not filecmp.cmp(out_dst_part.name, dst_part):
+              raise error.PayloadError(
+                  'Resulting %s partition corrupted.' % part_name)
 
         # Close the output files. If args.out_dst_* was not given, then these
         # files are created as temp files and will be deleted upon close().
-        out_dst_kern.close()
-        out_dst_root.close()
-
+        for handle in file_handles:
+          handle.close()
     except error.PayloadError, e:
       sys.stderr.write('Error: %s\n' % e)
       return 1
diff --git a/scripts/payload_info_unittest.py b/scripts/payload_info_unittest.py
new file mode 100755
index 0000000..a4ee9d5
--- /dev/null
+++ b/scripts/payload_info_unittest.py
@@ -0,0 +1,357 @@
+#!/usr/bin/python2
+#
+# Copyright (C) 2015 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.
+#
+
+"""Unit testing payload_info.py."""
+
+from __future__ import print_function
+
+import StringIO
+import collections
+import mock
+import sys
+import unittest
+
+import payload_info
+import update_payload
+
+from contextlib import contextmanager
+
+from update_payload import update_metadata_pb2
+
+class FakePayloadError(Exception):
+  """A generic error when using the FakePayload."""
+
+class FakeOption(object):
+  """Fake options object for testing."""
+
+  def __init__(self, **kwargs):
+    self.list_ops = False
+    self.stats = False
+    self.signatures = False
+    for key, val in kwargs.iteritems():
+      setattr(self, key, val)
+    if not hasattr(self, 'payload_file'):
+      self.payload_file = None
+
+class FakeOp(object):
+  """Fake manifest operation for testing."""
+
+  def __init__(self, src_extents, dst_extents, op_type, **kwargs):
+    self.src_extents = src_extents
+    self.dst_extents = dst_extents
+    self.type = op_type
+    for key, val in kwargs.iteritems():
+      setattr(self, key, val)
+
+  def HasField(self, field):
+    return hasattr(self, field)
+
+class FakePartition(object):
+  """Fake PartitionUpdate field for testing."""
+
+  def __init__(self, partition_name, operations):
+    self.partition_name = partition_name
+    self.operations = operations
+
+class FakeManifest(object):
+  """Fake manifest for testing."""
+
+  def __init__(self, major_version):
+    FakeExtent = collections.namedtuple('FakeExtent',
+                                        ['start_block', 'num_blocks'])
+    self.install_operations = [FakeOp([],
+                                      [FakeExtent(1, 1), FakeExtent(2, 2)],
+                                      update_payload.common.OpType.REPLACE_BZ,
+                                      dst_length=3*4096,
+                                      data_offset=1,
+                                      data_length=1)]
+    self.kernel_install_operations = [FakeOp(
+        [FakeExtent(1, 1)],
+        [FakeExtent(x, x) for x in xrange(20)],
+        update_payload.common.OpType.SOURCE_COPY,
+        src_length=4096)]
+    if major_version == payload_info.MAJOR_PAYLOAD_VERSION_BRILLO:
+      self.partitions = [FakePartition('root', self.install_operations),
+                         FakePartition('kernel',
+                                       self.kernel_install_operations)]
+      self.install_operations = self.kernel_install_operations = []
+    self.block_size = 4096
+    self.minor_version = 4
+    FakePartInfo = collections.namedtuple('FakePartInfo', ['size'])
+    self.old_rootfs_info = FakePartInfo(1 * 4096)
+    self.old_kernel_info = FakePartInfo(2 * 4096)
+    self.new_rootfs_info = FakePartInfo(3 * 4096)
+    self.new_kernel_info = FakePartInfo(4 * 4096)
+    self.signatures_offset = None
+    self.signatures_size = None
+
+  def HasField(self, field_name):
+    """Fake HasField method based on the python members."""
+    return hasattr(self, field_name) and getattr(self, field_name) is not None
+
+class FakeHeader(object):
+  """Fake payload header for testing."""
+
+  def __init__(self, version, manifest_len, metadata_signature_len):
+    self.version = version
+    self.manifest_len = manifest_len
+    self.metadata_signature_len = metadata_signature_len
+
+  @property
+  def size(self):
+    return (20 if self.version == payload_info.MAJOR_PAYLOAD_VERSION_CHROMEOS
+            else 24)
+
+class FakePayload(object):
+  """Fake payload for testing."""
+
+  def __init__(self, major_version):
+    self._header = FakeHeader(major_version, 222, 0)
+    self.header = None
+    self._manifest = FakeManifest(major_version)
+    self.manifest = None
+
+    self._blobs = {}
+    self._payload_signatures = update_metadata_pb2.Signatures()
+    self._metadata_signatures = update_metadata_pb2.Signatures()
+
+  def Init(self):
+    """Fake Init that sets header and manifest.
+
+    Failing to call Init() will not make header and manifest available to the
+    test.
+    """
+    self.header = self._header
+    self.manifest = self._manifest
+
+  def ReadDataBlob(self, offset, length):
+    """Return the blob that should be present at the offset location"""
+    if not offset in self._blobs:
+      raise FakePayloadError('Requested blob at unknown offset %d' % offset)
+    blob = self._blobs[offset]
+    if len(blob) != length:
+      raise FakePayloadError('Read blob with the wrong length (expect: %d, '
+                             'actual: %d)' % (len(blob), length))
+    return blob
+
+  @staticmethod
+  def _AddSignatureToProto(proto, **kwargs):
+    """Add a new Signature element to the passed proto."""
+    new_signature = proto.signatures.add()
+    for key, val in kwargs.iteritems():
+      setattr(new_signature, key, val)
+
+  def AddPayloadSignature(self, **kwargs):
+    self._AddSignatureToProto(self._payload_signatures, **kwargs)
+    blob = self._payload_signatures.SerializeToString()
+    self._manifest.signatures_offset = 1234
+    self._manifest.signatures_size = len(blob)
+    self._blobs[self._manifest.signatures_offset] = blob
+
+  def AddMetadataSignature(self, **kwargs):
+    self._AddSignatureToProto(self._metadata_signatures, **kwargs)
+    if self._header.metadata_signature_len:
+      del self._blobs[-self._header.metadata_signature_len]
+    blob = self._metadata_signatures.SerializeToString()
+    self._header.metadata_signature_len = len(blob)
+    self._blobs[-len(blob)] = blob
+
+class PayloadCommandTest(unittest.TestCase):
+  """Test class for our PayloadCommand class."""
+
+  @contextmanager
+  def OutputCapturer(self):
+    """A tool for capturing the sys.stdout"""
+    stdout = sys.stdout
+    try:
+      sys.stdout = StringIO.StringIO()
+      yield sys.stdout
+    finally:
+      sys.stdout = stdout
+
+  def TestCommand(self, payload_cmd, payload, expected_out):
+    """A tool for testing a payload command.
+
+    It tests that a payload command which runs with a given payload produces a
+    correct output.
+    """
+    with mock.patch.object(update_payload, 'Payload', return_value=payload), \
+         self.OutputCapturer() as output:
+      payload_cmd.Run()
+    self.assertEquals(output.getvalue(), expected_out)
+
+  def testDisplayValue(self):
+    """Verify that DisplayValue prints what we expect."""
+    with self.OutputCapturer() as output:
+      payload_info.DisplayValue('key', 'value')
+    self.assertEquals(output.getvalue(), 'key:                         value\n')
+
+  def testRun(self):
+    """Verify that Run parses and displays the payload like we expect."""
+    payload_cmd = payload_info.PayloadCommand(FakeOption(action='show'))
+    payload = FakePayload(payload_info.MAJOR_PAYLOAD_VERSION_CHROMEOS)
+    expected_out = """Payload version:             1
+Manifest length:             222
+Number of operations:        1
+Number of kernel ops:        1
+Block size:                  4096
+Minor version:               4
+"""
+    self.TestCommand(payload_cmd, payload, expected_out)
+
+  def testListOpsOnVersion1(self):
+    """Verify that the --list_ops option gives the correct output."""
+    payload_cmd = payload_info.PayloadCommand(
+        FakeOption(list_ops=True, action='show'))
+    payload = FakePayload(payload_info.MAJOR_PAYLOAD_VERSION_CHROMEOS)
+    expected_out = """Payload version:             1
+Manifest length:             222
+Number of operations:        1
+Number of kernel ops:        1
+Block size:                  4096
+Minor version:               4
+
+Install operations:
+  0: REPLACE_BZ
+    Data offset: 1
+    Data length: 1
+    Destination: 2 extents (3 blocks)
+      (1,1) (2,2)
+Kernel install operations:
+  0: SOURCE_COPY
+    Source: 1 extent (1 block)
+      (1,1)
+    Destination: 20 extents (190 blocks)
+      (0,0) (1,1) (2,2) (3,3) (4,4) (5,5) (6,6) (7,7) (8,8) (9,9) (10,10)
+      (11,11) (12,12) (13,13) (14,14) (15,15) (16,16) (17,17) (18,18) (19,19)
+"""
+    self.TestCommand(payload_cmd, payload, expected_out)
+
+  def testListOpsOnVersion2(self):
+    """Verify that the --list_ops option gives the correct output."""
+    payload_cmd = payload_info.PayloadCommand(
+        FakeOption(list_ops=True, action='show'))
+    payload = FakePayload(payload_info.MAJOR_PAYLOAD_VERSION_BRILLO)
+    expected_out = """Payload version:             2
+Manifest length:             222
+Number of partitions:        2
+  Number of "root" ops:      1
+  Number of "kernel" ops:    1
+Block size:                  4096
+Minor version:               4
+
+root install operations:
+  0: REPLACE_BZ
+    Data offset: 1
+    Data length: 1
+    Destination: 2 extents (3 blocks)
+      (1,1) (2,2)
+kernel install operations:
+  0: SOURCE_COPY
+    Source: 1 extent (1 block)
+      (1,1)
+    Destination: 20 extents (190 blocks)
+      (0,0) (1,1) (2,2) (3,3) (4,4) (5,5) (6,6) (7,7) (8,8) (9,9) (10,10)
+      (11,11) (12,12) (13,13) (14,14) (15,15) (16,16) (17,17) (18,18) (19,19)
+"""
+    self.TestCommand(payload_cmd, payload, expected_out)
+
+  def testStatsOnVersion1(self):
+    """Verify that the --stats option works correctly."""
+    payload_cmd = payload_info.PayloadCommand(
+        FakeOption(stats=True, action='show'))
+    payload = FakePayload(payload_info.MAJOR_PAYLOAD_VERSION_CHROMEOS)
+    expected_out = """Payload version:             1
+Manifest length:             222
+Number of operations:        1
+Number of kernel ops:        1
+Block size:                  4096
+Minor version:               4
+Blocks read:                 11
+Blocks written:              193
+Seeks when writing:          18
+"""
+    self.TestCommand(payload_cmd, payload, expected_out)
+
+  def testStatsOnVersion2(self):
+    """Verify that the --stats option works correctly on version 2."""
+    payload_cmd = payload_info.PayloadCommand(
+        FakeOption(stats=True, action='show'))
+    payload = FakePayload(payload_info.MAJOR_PAYLOAD_VERSION_BRILLO)
+    expected_out = """Payload version:             2
+Manifest length:             222
+Number of partitions:        2
+  Number of "root" ops:      1
+  Number of "kernel" ops:    1
+Block size:                  4096
+Minor version:               4
+Blocks read:                 11
+Blocks written:              193
+Seeks when writing:          18
+"""
+    self.TestCommand(payload_cmd, payload, expected_out)
+
+  def testEmptySignatures(self):
+    """Verify that the --signatures option works with unsigned payloads."""
+    payload_cmd = payload_info.PayloadCommand(
+        FakeOption(action='show', signatures=True))
+    payload = FakePayload(payload_info.MAJOR_PAYLOAD_VERSION_CHROMEOS)
+    expected_out = """Payload version:             1
+Manifest length:             222
+Number of operations:        1
+Number of kernel ops:        1
+Block size:                  4096
+Minor version:               4
+No metadata signatures stored in the payload
+No payload signatures stored in the payload
+"""
+    self.TestCommand(payload_cmd, payload, expected_out)
+
+  def testSignatures(self):
+    """Verify that the --signatures option shows the present signatures."""
+    payload_cmd = payload_info.PayloadCommand(
+        FakeOption(action='show', signatures=True))
+    payload = FakePayload(payload_info.MAJOR_PAYLOAD_VERSION_BRILLO)
+    payload.AddPayloadSignature(version=1,
+                                data='12345678abcdefgh\x00\x01\x02\x03')
+    payload.AddPayloadSignature(data='I am a signature so access is yes.')
+    payload.AddMetadataSignature(data='\x00\x0a\x0c')
+    expected_out = """Payload version:             2
+Manifest length:             222
+Number of partitions:        2
+  Number of "root" ops:      1
+  Number of "kernel" ops:    1
+Block size:                  4096
+Minor version:               4
+Metadata signatures blob:    file_offset=246 (7 bytes)
+Metadata signatures: (1 entries)
+  version=None, hex_data: (3 bytes)
+    00 0a 0c                                        | ...
+Payload signatures blob:     blob_offset=1234 (64 bytes)
+Payload signatures: (2 entries)
+  version=1, hex_data: (20 bytes)
+    31 32 33 34 35 36 37 38 61 62 63 64 65 66 67 68 | 12345678abcdefgh
+    00 01 02 03                                     | ....
+  version=None, hex_data: (34 bytes)
+    49 20 61 6d 20 61 20 73 69 67 6e 61 74 75 72 65 | I am a signature
+    20 73 6f 20 61 63 63 65 73 73 20 69 73 20 79 65 |  so access is ye
+    73 2e                                           | s.
+"""
+    self.TestCommand(payload_cmd, payload, expected_out)
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/scripts/run_unittests b/scripts/run_unittests
index c8e713d..0d301ba 100755
--- a/scripts/run_unittests
+++ b/scripts/run_unittests
@@ -25,4 +25,6 @@
   python -m update_payload."${filename%.*}"
 done
 
+./payload_info_unittest.py
+
 exit 0
diff --git a/scripts/test_paycheck.sh b/scripts/test_paycheck.sh
index e578f85..239b984 100755
--- a/scripts/test_paycheck.sh
+++ b/scripts/test_paycheck.sh
@@ -61,6 +61,7 @@
 NEW_DELTA_ROOT_PART=new_delta_root.part
 NEW_FULL_KERN_PART=new_full_kern.part
 NEW_FULL_ROOT_PART=new_full_root.part
+CROS_PARTS="kernel root"
 
 
 log() {
@@ -92,7 +93,8 @@
   out_dst_root_part="$2/$4"
 
   time ${paycheck} ${payload_file} \
-    --out_dst_kern ${out_dst_kern_part} --out_dst_root ${out_dst_root_part}
+    --part_names ${CROS_PARTS} \
+    --out_dst_part_paths ${out_dst_kern_part} ${out_dst_root_part}
 }
 
 apply_delta_payload() {
@@ -105,9 +107,10 @@
   src_root_part="$2/$8"
 
   time ${paycheck} ${payload_file} \
-    --out_dst_kern ${out_dst_kern_part} --out_dst_root ${out_dst_root_part} \
-    --dst_kern ${dst_kern_part} --dst_root ${dst_root_part} \
-    --src_kern ${src_kern_part} --src_root ${src_root_part}
+    --part_names ${CROS_PARTS} \
+    --out_dst_part_paths ${out_dst_kern_part} ${out_dst_root_part} \
+    --dst_part_paths ${dst_kern_part} ${dst_root_part} \
+    --src_part_paths ${src_kern_part} ${src_root_part}
 }
 
 main() {
diff --git a/scripts/update_payload/applier.py b/scripts/update_payload/applier.py
index 9582b3d..c63e156 100644
--- a/scripts/update_payload/applier.py
+++ b/scripts/update_payload/applier.py
@@ -622,46 +622,64 @@
       _VerifySha256(new_part_file, new_part_info.hash,
                     'new ' + part_name, length=new_part_info.size)
 
-  def Run(self, new_kernel_part, new_rootfs_part, old_kernel_part=None,
-          old_rootfs_part=None):
+  def Run(self, new_parts, old_parts=None):
     """Applier entry point, invoking all update operations.
 
     Args:
-      new_kernel_part: name of dest kernel partition file
-      new_rootfs_part: name of dest rootfs partition file
-      old_kernel_part: name of source kernel partition file (optional)
-      old_rootfs_part: name of source rootfs partition file (optional)
+      new_parts: map of partition name to dest partition file
+      old_parts: map of partition name to source partition file (optional)
 
     Raises:
       PayloadError if payload application failed.
     """
+    if old_parts is None:
+      old_parts = {}
+
     self.payload.ResetFile()
 
-    # Make sure the arguments are sane and match the payload.
-    if not (new_kernel_part and new_rootfs_part):
-      raise PayloadError('missing dst {kernel,rootfs} partitions')
+    new_part_info = {}
+    old_part_info = {}
+    install_operations = []
 
-    if not (old_kernel_part or old_rootfs_part):
-      if not self.payload.IsFull():
-        raise PayloadError('trying to apply a non-full update without src '
-                           '{kernel,rootfs} partitions')
-    elif old_kernel_part and old_rootfs_part:
-      if not self.payload.IsDelta():
-        raise PayloadError('trying to apply a non-delta update onto src '
-                           '{kernel,rootfs} partitions')
+    manifest = self.payload.manifest
+    if self.payload.header.version == 1:
+      for real_name, proto_name in common.CROS_PARTITIONS:
+        new_part_info[real_name] = getattr(manifest, 'new_%s_info' % proto_name)
+        old_part_info[real_name] = getattr(manifest, 'old_%s_info' % proto_name)
+
+      install_operations.append((common.ROOTFS, manifest.install_operations))
+      install_operations.append((common.KERNEL,
+                                 manifest.kernel_install_operations))
+    else:
+      for part in manifest.partitions:
+        name = part.partition_name
+        new_part_info[name] = part.new_partition_info
+        old_part_info[name] = part.old_partition_info
+        install_operations.append((name, part.operations))
+
+    part_names = set(new_part_info.keys())  # Equivalently, old_part_info.keys()
+
+    # Make sure the arguments are sane and match the payload.
+    new_part_names = set(new_parts.keys())
+    if new_part_names != part_names:
+      raise PayloadError('missing dst partition(s) %s' %
+                         ', '.join(part_names - new_part_names))
+
+    old_part_names = set(old_parts.keys())
+    if part_names - old_part_names:
+      if self.payload.IsDelta():
+        raise PayloadError('trying to apply a delta update without src '
+                           'partition(s) %s' %
+                           ', '.join(part_names - old_part_names))
+    elif old_part_names == part_names:
+      if self.payload.IsFull():
+        raise PayloadError('trying to apply a full update onto src partitions')
     else:
       raise PayloadError('not all src partitions provided')
 
-    # Apply update to rootfs.
-    self._ApplyToPartition(
-        self.payload.manifest.install_operations, 'rootfs',
-        'install_operations', new_rootfs_part,
-        self.payload.manifest.new_rootfs_info, old_rootfs_part,
-        self.payload.manifest.old_rootfs_info)
+    for name, operations in install_operations:
+      # Apply update to partition.
+      self._ApplyToPartition(
+          operations, name, '%s_install_operations' % name, new_parts[name],
+          new_part_info[name], old_parts.get(name, None), old_part_info[name])
 
-    # Apply update to kernel update.
-    self._ApplyToPartition(
-        self.payload.manifest.kernel_install_operations, 'kernel',
-        'kernel_install_operations', new_kernel_part,
-        self.payload.manifest.new_kernel_info, old_kernel_part,
-        self.payload.manifest.old_kernel_info)
diff --git a/scripts/update_payload/checker.py b/scripts/update_payload/checker.py
index e9e9958..746d4be 100644
--- a/scripts/update_payload/checker.py
+++ b/scripts/update_payload/checker.py
@@ -28,6 +28,7 @@
 
 import array
 import base64
+import collections
 import hashlib
 import itertools
 import os
@@ -330,15 +331,12 @@
     # Reset state; these will be assigned when the manifest is checked.
     self.sigs_offset = 0
     self.sigs_size = 0
-    self.old_rootfs_fs_size = 0
-    self.old_kernel_fs_size = 0
-    self.new_rootfs_fs_size = 0
-    self.new_kernel_fs_size = 0
+    self.old_part_info = {}
+    self.new_part_info = {}
+    self.new_fs_sizes = collections.defaultdict(int)
+    self.old_fs_sizes = collections.defaultdict(int)
     self.minor_version = None
-    # TODO(*): When fixing crbug.com/794404, the major version should be
-    # correctly handled in update_payload scripts. So stop forcing
-    # major_verions=1 here and set it to the correct value.
-    self.major_version = 1
+    self.major_version = None
 
   @staticmethod
   def _CheckElem(msg, name, report, is_mandatory, is_submsg, convert=str,
@@ -368,22 +366,56 @@
     Raises:
       error.PayloadError if a mandatory element is missing.
     """
+    element_result = collections.namedtuple('element_result', ['msg', 'report'])
+
     if not msg.HasField(name):
       if is_mandatory:
         raise error.PayloadError('%smissing mandatory %s %r.' %
                                  (msg_name + ' ' if msg_name else '',
                                   'sub-message' if is_submsg else 'field',
                                   name))
-      return None, None
+      return element_result(None, None)
 
     value = getattr(msg, name)
     if is_submsg:
-      return value, report and report.AddSubReport(name)
+      return element_result(value, report and report.AddSubReport(name))
     else:
       if report:
         report.AddField(name, convert(value), linebreak=linebreak,
                         indent=indent)
-      return value, None
+      return element_result(value, None)
+
+  @staticmethod
+  def _CheckRepeatedElemNotPresent(msg, field_name, msg_name):
+    """Checks that a repeated element is not specified in the message.
+
+    Args:
+      msg: The message containing the element.
+      field_name: The name of the element.
+      msg_name: The name of the message object (for error reporting).
+
+    Raises:
+      error.PayloadError if the repeated element is present or non-empty.
+    """
+    if getattr(msg, field_name, None):
+      raise error.PayloadError('%sfield %r not empty.' %
+                               (msg_name + ' ' if msg_name else '', field_name))
+
+  @staticmethod
+  def _CheckElemNotPresent(msg, field_name, msg_name):
+    """Checks that an element is not specified in the message.
+
+    Args:
+      msg: The message containing the element.
+      field_name: The name of the element.
+      msg_name: The name of the message object (for error reporting).
+
+    Raises:
+      error.PayloadError if the repeated element is present.
+    """
+    if msg.HasField(field_name):
+      raise error.PayloadError('%sfield %r exists.' %
+                               (msg_name + ' ' if msg_name else '', field_name))
 
   @staticmethod
   def _CheckMandatoryField(msg, field_name, report, msg_name, convert=str,
@@ -433,6 +465,22 @@
                                 ' in ' + obj_name if obj_name else ''))
 
   @staticmethod
+  def _CheckPresentIffMany(vals, name, obj_name):
+    """Checks that a set of vals and names imply every other element.
+
+    Args:
+      vals: The set of values to be compared.
+      name: The name of the objects holding the corresponding value.
+      obj_name: Name of the object containing these values.
+
+    Raises:
+      error.PayloadError if assertion does not hold.
+    """
+    if any(vals) and not all(vals):
+      raise error.PayloadError('%r is not present in all values%s.' %
+                               (name, ' in ' + obj_name if obj_name else ''))
+
+  @staticmethod
   def _Run(cmd, send_data=None):
     """Runs a subprocess, returns its output.
 
@@ -544,13 +592,12 @@
       raise error.PayloadError('Unsupported minor version: %d' %
                                self.minor_version)
 
-  def _CheckManifest(self, report, rootfs_part_size=0, kernel_part_size=0):
+  def _CheckManifest(self, report, part_sizes=None):
     """Checks the payload manifest.
 
     Args:
       report: A report object to add to.
-      rootfs_part_size: Size of the rootfs partition in bytes.
-      kernel_part_size: Size of the kernel partition in bytes.
+      part_sizes: Map of partition label to partition size in bytes.
 
     Returns:
       A tuple consisting of the partition block size used during the update
@@ -559,6 +606,9 @@
     Raises:
       error.PayloadError if any of the checks fail.
     """
+    self.major_version = self.payload.header.version
+
+    part_sizes = collections.defaultdict(int, part_sizes)
     manifest = self.payload.manifest
     report.AddSection('manifest')
 
@@ -577,39 +627,57 @@
     self._CheckPresentIff(self.sigs_offset, self.sigs_size,
                           'signatures_offset', 'signatures_size', 'manifest')
 
-    # Check: old_kernel_info <==> old_rootfs_info.
-    oki_msg, oki_report = self._CheckOptionalSubMsg(manifest,
-                                                    'old_kernel_info', report)
-    ori_msg, ori_report = self._CheckOptionalSubMsg(manifest,
-                                                    'old_rootfs_info', report)
-    self._CheckPresentIff(oki_msg, ori_msg, 'old_kernel_info',
-                          'old_rootfs_info', 'manifest')
-    if oki_msg:  # equivalently, ori_msg
+    if self.major_version == 1:
+      for real_name, proto_name in common.CROS_PARTITIONS:
+        self.old_part_info[real_name] = self._CheckOptionalSubMsg(
+            manifest, 'old_%s_info' % proto_name, report)
+        self.new_part_info[real_name] = self._CheckMandatorySubMsg(
+            manifest, 'new_%s_info' % proto_name, report, 'manifest')
+
+      # Check: old_kernel_info <==> old_rootfs_info.
+      self._CheckPresentIff(self.old_part_info[common.KERNEL].msg,
+                            self.old_part_info[common.ROOTFS].msg,
+                            'old_kernel_info', 'old_rootfs_info', 'manifest')
+    else:
+      for part in manifest.partitions:
+        name = part.partition_name
+        self.old_part_info[name] = self._CheckOptionalSubMsg(
+            part, 'old_partition_info', report)
+        self.new_part_info[name] = self._CheckMandatorySubMsg(
+            part, 'new_partition_info', report, 'manifest.partitions')
+
+      # Check: Old-style partition infos should not be specified.
+      for _, part in common.CROS_PARTITIONS:
+        self._CheckElemNotPresent(manifest, 'old_%s_info' % part, 'manifest')
+        self._CheckElemNotPresent(manifest, 'new_%s_info' % part, 'manifest')
+
+      # Check: If old_partition_info is specified anywhere, it must be
+      # specified everywhere.
+      old_part_msgs = [part.msg for part in self.old_part_info.values() if part]
+      self._CheckPresentIffMany(old_part_msgs, 'old_partition_info',
+                                'manifest.partitions')
+
+    is_delta = any(part and part.msg for part in self.old_part_info.values())
+    if is_delta:
       # Assert/mark delta payload.
       if self.payload_type == _TYPE_FULL:
         raise error.PayloadError(
             'Apparent full payload contains old_{kernel,rootfs}_info.')
       self.payload_type = _TYPE_DELTA
 
-      # Check: {size, hash} present in old_{kernel,rootfs}_info.
-      self.old_kernel_fs_size = self._CheckMandatoryField(
-          oki_msg, 'size', oki_report, 'old_kernel_info')
-      self._CheckMandatoryField(oki_msg, 'hash', oki_report, 'old_kernel_info',
-                                convert=common.FormatSha256)
-      self.old_rootfs_fs_size = self._CheckMandatoryField(
-          ori_msg, 'size', ori_report, 'old_rootfs_info')
-      self._CheckMandatoryField(ori_msg, 'hash', ori_report, 'old_rootfs_info',
-                                convert=common.FormatSha256)
+      for part, (msg, part_report) in self.old_part_info.iteritems():
+        # Check: {size, hash} present in old_{kernel,rootfs}_info.
+        field = 'old_%s_info' % part
+        self.old_fs_sizes[part] = self._CheckMandatoryField(msg, 'size',
+                                                            part_report, field)
+        self._CheckMandatoryField(msg, 'hash', part_report, field,
+                                  convert=common.FormatSha256)
 
-      # Check: old_{kernel,rootfs} size must fit in respective partition.
-      if kernel_part_size and self.old_kernel_fs_size > kernel_part_size:
-        raise error.PayloadError(
-            'Old kernel content (%d) exceed partition size (%d).' %
-            (self.old_kernel_fs_size, kernel_part_size))
-      if rootfs_part_size and self.old_rootfs_fs_size > rootfs_part_size:
-        raise error.PayloadError(
-            'Old rootfs content (%d) exceed partition size (%d).' %
-            (self.old_rootfs_fs_size, rootfs_part_size))
+        # Check: old_{kernel,rootfs} size must fit in respective partition.
+        if self.old_fs_sizes[part] > part_sizes[part] > 0:
+          raise error.PayloadError(
+              'Old %s content (%d) exceed partition size (%d).' %
+              (part, self.old_fs_sizes[part], part_sizes[part]))
     else:
       # Assert/mark full payload.
       if self.payload_type == _TYPE_DELTA:
@@ -617,31 +685,19 @@
             'Apparent delta payload missing old_{kernel,rootfs}_info.')
       self.payload_type = _TYPE_FULL
 
-    # Check: new_kernel_info present; contains {size, hash}.
-    nki_msg, nki_report = self._CheckMandatorySubMsg(
-        manifest, 'new_kernel_info', report, 'manifest')
-    self.new_kernel_fs_size = self._CheckMandatoryField(
-        nki_msg, 'size', nki_report, 'new_kernel_info')
-    self._CheckMandatoryField(nki_msg, 'hash', nki_report, 'new_kernel_info',
-                              convert=common.FormatSha256)
+    # Check: new_{kernel,rootfs}_info present; contains {size, hash}.
+    for part, (msg, part_report) in self.new_part_info.iteritems():
+      field = 'new_%s_info' % part
+      self.new_fs_sizes[part] = self._CheckMandatoryField(msg, 'size',
+                                                          part_report, field)
+      self._CheckMandatoryField(msg, 'hash', part_report, field,
+                                convert=common.FormatSha256)
 
-    # Check: new_rootfs_info present; contains {size, hash}.
-    nri_msg, nri_report = self._CheckMandatorySubMsg(
-        manifest, 'new_rootfs_info', report, 'manifest')
-    self.new_rootfs_fs_size = self._CheckMandatoryField(
-        nri_msg, 'size', nri_report, 'new_rootfs_info')
-    self._CheckMandatoryField(nri_msg, 'hash', nri_report, 'new_rootfs_info',
-                              convert=common.FormatSha256)
-
-    # Check: new_{kernel,rootfs} size must fit in respective partition.
-    if kernel_part_size and self.new_kernel_fs_size > kernel_part_size:
-      raise error.PayloadError(
-          'New kernel content (%d) exceed partition size (%d).' %
-          (self.new_kernel_fs_size, kernel_part_size))
-    if rootfs_part_size and self.new_rootfs_fs_size > rootfs_part_size:
-      raise error.PayloadError(
-          'New rootfs content (%d) exceed partition size (%d).' %
-          (self.new_rootfs_fs_size, rootfs_part_size))
+      # Check: new_{kernel,rootfs} size must fit in respective partition.
+      if self.new_fs_sizes[part] > part_sizes[part] > 0:
+        raise error.PayloadError(
+            'New %s content (%d) exceed partition size (%d).' %
+            (part, self.new_fs_sizes[part], part_sizes[part]))
 
     # Check: minor_version makes sense for the payload type. This check should
     # run after the payload type has been set.
@@ -1231,17 +1287,16 @@
         raise error.PayloadError('Unknown signature version (%d).' %
                                  sig.version)
 
-  def Run(self, pubkey_file_name=None, metadata_sig_file=None,
-          rootfs_part_size=0, kernel_part_size=0, report_out_file=None):
+  def Run(self, pubkey_file_name=None, metadata_sig_file=None, metadata_size=0,
+          part_sizes=None, report_out_file=None):
     """Checker entry point, invoking all checks.
 
     Args:
       pubkey_file_name: Public key used for signature verification.
       metadata_sig_file: Metadata signature, if verification is desired.
-      rootfs_part_size: The size of rootfs partitions in bytes (default: infer
-                        based on payload type and version).
-      kernel_part_size: The size of kernel partitions in bytes (default: use
-                        reported filesystem size).
+      metadata_size: Metadata size, if verification is desired.
+      part_sizes: Mapping of partition label to size in bytes (default: infer
+        based on payload type and version or filesystem).
       report_out_file: File object to dump the report to.
 
     Raises:
@@ -1258,6 +1313,12 @@
     self.payload.ResetFile()
 
     try:
+      # Check metadata_size (if provided).
+      if metadata_size and self.payload.data_offset != metadata_size:
+        raise error.PayloadError('Invalid payload metadata size in payload(%d) '
+                                 'vs given(%d)' % (self.payload.data_offset,
+                                                   metadata_size))
+
       # Check metadata signature (if provided).
       if metadata_sig_file:
         metadata_sig = base64.b64decode(metadata_sig_file.read())
@@ -1268,52 +1329,64 @@
       # Part 1: Check the file header.
       report.AddSection('header')
       # Check: Payload version is valid.
-      if self.payload.header.version != 1:
+      if self.payload.header.version not in (1, 2):
         raise error.PayloadError('Unknown payload version (%d).' %
                                  self.payload.header.version)
       report.AddField('version', self.payload.header.version)
       report.AddField('manifest len', self.payload.header.manifest_len)
 
       # Part 2: Check the manifest.
-      self._CheckManifest(report, rootfs_part_size, kernel_part_size)
+      self._CheckManifest(report, part_sizes)
       assert self.payload_type, 'payload type should be known by now'
 
-      # Infer the usable partition size when validating rootfs operations:
-      # - If rootfs partition size was provided, use that.
-      # - Otherwise, if this is an older delta (minor version < 2), stick with
-      #   a known constant size. This is necessary because older deltas may
-      #   exceed the filesystem size when moving data blocks around.
-      # - Otherwise, use the encoded filesystem size.
-      new_rootfs_usable_size = self.new_rootfs_fs_size
-      old_rootfs_usable_size = self.old_rootfs_fs_size
-      if rootfs_part_size:
-        new_rootfs_usable_size = rootfs_part_size
-        old_rootfs_usable_size = rootfs_part_size
-      elif self.payload_type == _TYPE_DELTA and self.minor_version in (None, 1):
-        new_rootfs_usable_size = _OLD_DELTA_USABLE_PART_SIZE
-        old_rootfs_usable_size = _OLD_DELTA_USABLE_PART_SIZE
+      manifest = self.payload.manifest
 
-      # Part 3: Examine rootfs operations.
-      # TODO(garnold)(chromium:243559) only default to the filesystem size if
-      # no explicit size provided *and* the partition size is not embedded in
-      # the payload; see issue for more details.
-      report.AddSection('rootfs operations')
-      total_blob_size = self._CheckOperations(
-          self.payload.manifest.install_operations, report,
-          'install_operations', self.old_rootfs_fs_size,
-          self.new_rootfs_fs_size, old_rootfs_usable_size,
-          new_rootfs_usable_size, 0, False)
+      # Part 3: Examine partition operations.
+      install_operations = []
+      if self.major_version == 1:
+        # partitions field should not ever exist in major version 1 payloads
+        self._CheckRepeatedElemNotPresent(manifest, 'partitions', 'manifest')
 
-      # Part 4: Examine kernel operations.
-      # TODO(garnold)(chromium:243559) as above.
-      report.AddSection('kernel operations')
-      total_blob_size += self._CheckOperations(
-          self.payload.manifest.kernel_install_operations, report,
-          'kernel_install_operations', self.old_kernel_fs_size,
-          self.new_kernel_fs_size,
-          kernel_part_size if kernel_part_size else self.old_kernel_fs_size,
-          kernel_part_size if kernel_part_size else self.new_kernel_fs_size,
-          total_blob_size, True)
+        install_operations.append((common.ROOTFS, manifest.install_operations))
+        install_operations.append((common.KERNEL,
+                                   manifest.kernel_install_operations))
+
+      else:
+        self._CheckRepeatedElemNotPresent(manifest, 'install_operations',
+                                          'manifest')
+        self._CheckRepeatedElemNotPresent(manifest, 'kernel_install_operations',
+                                          'manifest')
+
+        for update in manifest.partitions:
+          install_operations.append((update.partition_name, update.operations))
+
+      total_blob_size = 0
+      for part, operations in install_operations:
+        report.AddSection('%s operations' % part)
+
+        new_fs_usable_size = self.new_fs_sizes[part]
+        old_fs_usable_size = self.old_fs_sizes[part]
+
+        if part_sizes.get(part, None):
+          new_fs_usable_size = old_fs_usable_size = part_sizes[part]
+        # Infer the usable partition size when validating rootfs operations:
+        # - If rootfs partition size was provided, use that.
+        # - Otherwise, if this is an older delta (minor version < 2), stick with
+        #   a known constant size. This is necessary because older deltas may
+        #   exceed the filesystem size when moving data blocks around.
+        # - Otherwise, use the encoded filesystem size.
+        elif self.payload_type == _TYPE_DELTA and part == common.ROOTFS and \
+            self.minor_version in (None, 1):
+          new_fs_usable_size = old_fs_usable_size = _OLD_DELTA_USABLE_PART_SIZE
+
+        # TODO(garnold)(chromium:243559) only default to the filesystem size if
+        # no explicit size provided *and* the partition size is not embedded in
+        # the payload; see issue for more details.
+        total_blob_size += self._CheckOperations(
+            operations, report, '%s_install_operations' % part,
+            self.old_fs_sizes[part], self.new_fs_sizes[part],
+            old_fs_usable_size, new_fs_usable_size, total_blob_size,
+            self.major_version == 1 and part == common.KERNEL)
 
       # Check: Operations data reach the end of the payload file.
       used_payload_size = self.payload.data_offset + total_blob_size
@@ -1322,11 +1395,11 @@
             'Used payload size (%d) different from actual file size (%d).' %
             (used_payload_size, payload_file_size))
 
-      # Part 5: Handle payload signatures message.
+      # Part 4: Handle payload signatures message.
       if self.check_payload_sig and self.sigs_size:
         self._CheckSignatures(report, pubkey_file_name)
 
-      # Part 6: Summary.
+      # Part 5: Summary.
       report.AddSection('summary')
       report.AddField('update type', self.payload_type)
 
diff --git a/scripts/update_payload/checker_unittest.py b/scripts/update_payload/checker_unittest.py
index f718234..98bf612 100755
--- a/scripts/update_payload/checker_unittest.py
+++ b/scripts/update_payload/checker_unittest.py
@@ -485,13 +485,16 @@
                    fail_bad_nki or fail_bad_nri or fail_old_kernel_fs_size or
                    fail_old_rootfs_fs_size or fail_new_kernel_fs_size or
                    fail_new_rootfs_fs_size)
+    part_sizes = {
+        common.ROOTFS: rootfs_part_size,
+        common.KERNEL: kernel_part_size
+    }
+
     if should_fail:
       self.assertRaises(PayloadError, payload_checker._CheckManifest, report,
-                        rootfs_part_size, kernel_part_size)
+                        part_sizes)
     else:
-      self.assertIsNone(payload_checker._CheckManifest(report,
-                                                       rootfs_part_size,
-                                                       kernel_part_size))
+      self.assertIsNone(payload_checker._CheckManifest(report, part_sizes))
 
   def testCheckLength(self):
     """Tests _CheckLength()."""
@@ -888,6 +891,8 @@
                           self.NewExtentList((1, 16)))
         total_src_blocks = 16
 
+    # TODO(tbrindus): add major version 2 tests.
+    payload_checker.major_version = 1
     if op_type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ):
       payload_checker.minor_version = 0
     elif op_type in (common.OpType.MOVE, common.OpType.BSDIFF):
@@ -1085,7 +1090,10 @@
     report = checker._PayloadReport()
 
     # We have to check the manifest first in order to set signature attributes.
-    payload_checker._CheckManifest(report, rootfs_part_size, kernel_part_size)
+    payload_checker._CheckManifest(report, {
+        common.ROOTFS: rootfs_part_size,
+        common.KERNEL: kernel_part_size
+    })
 
     should_fail = (fail_empty_sigs_blob or fail_missing_pseudo_op or
                    fail_mismatched_pseudo_op or fail_sig_missing_fields or
@@ -1127,8 +1135,8 @@
 
   def DoRunTest(self, rootfs_part_size_provided, kernel_part_size_provided,
                 fail_wrong_payload_type, fail_invalid_block_size,
-                fail_mismatched_block_size, fail_excess_data,
-                fail_rootfs_part_size_exceeded,
+                fail_mismatched_metadata_size, fail_mismatched_block_size,
+                fail_excess_data, fail_rootfs_part_size_exceeded,
                 fail_kernel_part_size_exceeded):
     """Tests Run()."""
     # Generate a test payload. For this test, we generate a full update that
@@ -1178,6 +1186,11 @@
     else:
       use_block_size = block_size
 
+    # For the unittests 246 is the value that generated for the payload.
+    metadata_size = 246
+    if fail_mismatched_metadata_size:
+      metadata_size += 1
+
     kwargs = {
         'payload_gen_dargs': {
             'privkey_file_name': test_utils._PRIVKEY_FILE_NAME,
@@ -1194,11 +1207,15 @@
       payload_checker = _GetPayloadChecker(payload_gen.WriteToFileWithData,
                                            **kwargs)
 
-      kwargs = {'pubkey_file_name': test_utils._PUBKEY_FILE_NAME,
-                'rootfs_part_size': rootfs_part_size,
-                'kernel_part_size': kernel_part_size}
+      kwargs = {
+          'pubkey_file_name': test_utils._PUBKEY_FILE_NAME,
+          'metadata_size': metadata_size,
+          'part_sizes': {
+              common.KERNEL: kernel_part_size,
+              common.ROOTFS: rootfs_part_size}}
+
       should_fail = (fail_wrong_payload_type or fail_mismatched_block_size or
-                     fail_excess_data or
+                     fail_mismatched_metadata_size or fail_excess_data or
                      fail_rootfs_part_size_exceeded or
                      fail_kernel_part_size_exceeded)
       if should_fail:
@@ -1353,6 +1370,7 @@
                       'kernel_part_size_provided': (True, False),
                       'fail_wrong_payload_type': (True, False),
                       'fail_invalid_block_size': (True, False),
+                      'fail_mismatched_metadata_size': (True, False),
                       'fail_mismatched_block_size': (True, False),
                       'fail_excess_data': (True, False),
                       'fail_rootfs_part_size_exceeded': (True, False),
diff --git a/scripts/update_payload/common.py b/scripts/update_payload/common.py
index 4e7b2e3..9061a75 100644
--- a/scripts/update_payload/common.py
+++ b/scripts/update_payload/common.py
@@ -42,6 +42,11 @@
 BROTLI_BSDIFF_MINOR_PAYLOAD_VERSION = 4
 PUFFDIFF_MINOR_PAYLOAD_VERSION = 5
 
+KERNEL = 'kernel'
+ROOTFS = 'root'
+# Tuple of (name in system, name in protobuf).
+CROS_PARTITIONS = ((KERNEL, KERNEL), (ROOTFS, 'rootfs'))
+
 #
 # Payload operation types.
 #
diff --git a/scripts/update_payload/payload.py b/scripts/update_payload/payload.py
index 380d6d0..2a0cb58 100644
--- a/scripts/update_payload/payload.py
+++ b/scripts/update_payload/payload.py
@@ -273,19 +273,19 @@
     return not self.IsDelta()
 
   def Check(self, pubkey_file_name=None, metadata_sig_file=None,
-            report_out_file=None, assert_type=None, block_size=0,
-            rootfs_part_size=0, kernel_part_size=0, allow_unhashed=False,
+            metadata_size=0, report_out_file=None, assert_type=None,
+            block_size=0, part_sizes=None, allow_unhashed=False,
             disabled_tests=()):
     """Checks the payload integrity.
 
     Args:
       pubkey_file_name: public key used for signature verification
       metadata_sig_file: metadata signature, if verification is desired
+      metadata_size: metadata size, if verification is desired
       report_out_file: file object to dump the report to
       assert_type: assert that payload is either 'full' or 'delta'
       block_size: expected filesystem / payload block size
-      rootfs_part_size: the size of (physical) rootfs partitions in bytes
-      kernel_part_size: the size of (physical) kernel partitions in bytes
+      part_sizes: map of partition label to (physical) size in bytes
       allow_unhashed: allow unhashed operation blobs
       disabled_tests: list of tests to disable
 
@@ -300,20 +300,18 @@
         allow_unhashed=allow_unhashed, disabled_tests=disabled_tests)
     helper.Run(pubkey_file_name=pubkey_file_name,
                metadata_sig_file=metadata_sig_file,
-               rootfs_part_size=rootfs_part_size,
-               kernel_part_size=kernel_part_size,
+               metadata_size=metadata_size,
+               part_sizes=part_sizes,
                report_out_file=report_out_file)
 
-  def Apply(self, new_kernel_part, new_rootfs_part, old_kernel_part=None,
-            old_rootfs_part=None, bsdiff_in_place=True, bspatch_path=None,
-            puffpatch_path=None, truncate_to_expected_size=True):
+  def Apply(self, new_parts, old_parts=None, bsdiff_in_place=True,
+            bspatch_path=None, puffpatch_path=None,
+            truncate_to_expected_size=True):
     """Applies the update payload.
 
     Args:
-      new_kernel_part: name of dest kernel partition file
-      new_rootfs_part: name of dest rootfs partition file
-      old_kernel_part: name of source kernel partition file (optional)
-      old_rootfs_part: name of source rootfs partition file (optional)
+      new_parts: map of partition name to dest partition file
+      old_parts: map of partition name to partition file (optional)
       bsdiff_in_place: whether to perform BSDIFF operations in-place (optional)
       bspatch_path: path to the bspatch binary (optional)
       puffpatch_path: path to the puffpatch binary (optional)
@@ -331,6 +329,4 @@
         self, bsdiff_in_place=bsdiff_in_place, bspatch_path=bspatch_path,
         puffpatch_path=puffpatch_path,
         truncate_to_expected_size=truncate_to_expected_size)
-    helper.Run(new_kernel_part, new_rootfs_part,
-               old_kernel_part=old_kernel_part,
-               old_rootfs_part=old_rootfs_part)
+    helper.Run(new_parts, old_parts=old_parts)
diff --git a/tar_bunzip2.gypi b/tar_bunzip2.gypi
index 8c6614a..4d1be28 100644
--- a/tar_bunzip2.gypi
+++ b/tar_bunzip2.gypi
@@ -21,9 +21,6 @@
     {
       'rule_name': 'tar-bunzip2',
       'extension': 'bz2',
-      'inputs': [
-        '<(RULE_INPUT_PATH)',
-      ],
       'outputs': [
         # The .flag file is used to mark the timestamp of the file extraction
         # and re-run this action if a new .bz2 file is generated.
diff --git a/update_attempter.cc b/update_attempter.cc
index fa172df..db373ab 100644
--- a/update_attempter.cc
+++ b/update_attempter.cc
@@ -47,6 +47,7 @@
 #include "update_engine/common/prefs_interface.h"
 #include "update_engine/common/subprocess.h"
 #include "update_engine/common/utils.h"
+#include "update_engine/connection_manager_interface.h"
 #include "update_engine/libcurl_http_fetcher.h"
 #include "update_engine/metrics_reporter_interface.h"
 #include "update_engine/omaha_request_action.h"
@@ -59,7 +60,9 @@
 #include "update_engine/payload_state_interface.h"
 #include "update_engine/power_manager_interface.h"
 #include "update_engine/system_state.h"
+#include "update_engine/update_boot_flags_action.h"
 #include "update_engine/update_manager/policy.h"
+#include "update_engine/update_manager/policy_utils.h"
 #include "update_engine/update_manager/update_manager.h"
 #include "update_engine/update_status_utils.h"
 
@@ -72,6 +75,8 @@
 using chromeos_update_manager::EvalStatus;
 using chromeos_update_manager::Policy;
 using chromeos_update_manager::UpdateCheckParams;
+using chromeos_update_manager::CalculateStagingCase;
+using chromeos_update_manager::StagingCase;
 using std::set;
 using std::shared_ptr;
 using std::string;
@@ -238,6 +243,7 @@
                              const string& omaha_url,
                              const string& target_channel,
                              const string& target_version_prefix,
+                             bool rollback_allowed,
                              bool obey_proxies,
                              bool interactive) {
   // This is normally called frequently enough so it's appropriate to use as a
@@ -246,10 +252,6 @@
   // timeout event.
   CheckAndReportDailyMetrics();
 
-  // Notify of the new update attempt, clearing prior interactive requests.
-  if (forced_update_pending_callback_.get())
-    forced_update_pending_callback_->Run(false, false);
-
   fake_update_success_ = false;
   if (status_ == UpdateStatus::UPDATED_NEED_REBOOT) {
     // Although we have applied an update, we still want to ping Omaha
@@ -276,6 +278,7 @@
                              omaha_url,
                              target_channel,
                              target_version_prefix,
+                             rollback_allowed,
                              obey_proxies,
                              interactive)) {
     return;
@@ -290,10 +293,7 @@
   // checks in the case where a response is not received.
   UpdateLastCheckedTime();
 
-  // Just in case we didn't update boot flags yet, make sure they're updated
-  // before any update processing starts.
-  start_action_processor_ = true;
-  UpdateBootFlags();
+  ScheduleProcessingStart();
 }
 
 void UpdateAttempter::RefreshDevicePolicy() {
@@ -351,6 +351,7 @@
                                             const string& omaha_url,
                                             const string& target_channel,
                                             const string& target_version_prefix,
+                                            bool rollback_allowed,
                                             bool obey_proxies,
                                             bool interactive) {
   http_response_code_ = 0;
@@ -359,10 +360,21 @@
   // Refresh the policy before computing all the update parameters.
   RefreshDevicePolicy();
 
+  // Check whether we need to clear the rollback-happened preference after
+  // policy is available again.
+  UpdateRollbackHappened();
+
   // Update the target version prefix.
   omaha_request_params_->set_target_version_prefix(target_version_prefix);
 
-  CalculateScatteringParams(interactive);
+  // Set whether rollback is allowed.
+  omaha_request_params_->set_rollback_allowed(rollback_allowed);
+
+  CalculateStagingParams(interactive);
+  // If staging_wait_time_ wasn't set, staging is off, use scattering instead.
+  if (staging_wait_time_.InSeconds() == 0) {
+    CalculateScatteringParams(interactive);
+  }
 
   CalculateP2PParams(interactive);
   if (payload_state->GetUsingP2PForDownloading() ||
@@ -407,6 +419,8 @@
 
   LOG(INFO) << "target_version_prefix = "
             << omaha_request_params_->target_version_prefix()
+            << ", rollback_allowed = "
+            << omaha_request_params_->rollback_allowed()
             << ", scatter_factor_in_seconds = "
             << utils::FormatSecs(scatter_factor_.InSeconds());
 
@@ -482,7 +496,8 @@
     if (omaha_request_params_->waiting_period().InSeconds() == 0) {
       // First case. Check if we have a suitable value to set for
       // the waiting period.
-      if (prefs_->GetInt64(kPrefsWallClockWaitPeriod, &wait_period_in_secs) &&
+      if (prefs_->GetInt64(kPrefsWallClockScatteringWaitPeriod,
+                           &wait_period_in_secs) &&
           wait_period_in_secs > 0 &&
           wait_period_in_secs <= scatter_factor_.InSeconds()) {
         // This means:
@@ -542,7 +557,7 @@
     omaha_request_params_->set_wall_clock_based_wait_enabled(false);
     omaha_request_params_->set_update_check_count_wait_enabled(false);
     omaha_request_params_->set_waiting_period(TimeDelta::FromSeconds(0));
-    prefs_->Delete(kPrefsWallClockWaitPeriod);
+    prefs_->Delete(kPrefsWallClockScatteringWaitPeriod);
     prefs_->Delete(kPrefsUpdateCheckCount);
     // Don't delete the UpdateFirstSeenAt file as we don't want manual checks
     // that result in no-updates (e.g. due to server side throttling) to
@@ -566,15 +581,43 @@
       omaha_request_params_->waiting_period());
 }
 
-void UpdateAttempter::BuildPostInstallActions(
-    InstallPlanAction* previous_action) {
-  shared_ptr<PostinstallRunnerAction> postinstall_runner_action(
-      new PostinstallRunnerAction(system_state_->boot_control(),
-                                  system_state_->hardware()));
-  postinstall_runner_action->set_delegate(this);
-  actions_.push_back(shared_ptr<AbstractAction>(postinstall_runner_action));
-  BondActions(previous_action,
-              postinstall_runner_action.get());
+void UpdateAttempter::CalculateStagingParams(bool interactive) {
+  bool oobe_complete = system_state_->hardware()->IsOOBEEnabled() &&
+                       system_state_->hardware()->IsOOBEComplete(nullptr);
+  auto device_policy = system_state_->device_policy();
+  StagingCase staging_case = StagingCase::kOff;
+  if (device_policy && !interactive && oobe_complete) {
+    staging_wait_time_ = omaha_request_params_->waiting_period();
+    staging_case = CalculateStagingCase(
+        device_policy, prefs_, &staging_wait_time_, &staging_schedule_);
+  }
+  switch (staging_case) {
+    case StagingCase::kOff:
+      // Staging is off, get rid of persisted value.
+      prefs_->Delete(kPrefsWallClockStagingWaitPeriod);
+      // Set |staging_wait_time_| to its default value so scattering can still
+      // be turned on
+      staging_wait_time_ = TimeDelta();
+      break;
+    // Let the cases fall through since they just add, and never remove, steps
+    // to turning staging on.
+    case StagingCase::kNoSavedValue:
+      prefs_->SetInt64(kPrefsWallClockStagingWaitPeriod,
+                       staging_wait_time_.InDays());
+    case StagingCase::kSetStagingFromPref:
+      omaha_request_params_->set_waiting_period(staging_wait_time_);
+    case StagingCase::kNoAction:
+      // Staging is on, enable wallclock based wait so that its values get used.
+      omaha_request_params_->set_wall_clock_based_wait_enabled(true);
+      // Use UpdateCheckCount if possible to prevent devices updating all at
+      // once.
+      omaha_request_params_->set_update_check_count_wait_enabled(
+          DecrementUpdateCheckCount());
+      // Scattering should not be turned on if staging is on, delete the
+      // existing scattering configuration.
+      prefs_->Delete(kPrefsWallClockScatteringWaitPeriod);
+      scatter_factor_ = TimeDelta();
+  }
 }
 
 void UpdateAttempter::BuildUpdateActions(bool interactive) {
@@ -582,82 +625,75 @@
   processor_->set_delegate(this);
 
   // Actions:
-  std::unique_ptr<LibcurlHttpFetcher> update_check_fetcher(
-      new LibcurlHttpFetcher(GetProxyResolver(), system_state_->hardware()));
+  auto update_check_fetcher = std::make_unique<LibcurlHttpFetcher>(
+      GetProxyResolver(), system_state_->hardware());
   update_check_fetcher->set_server_to_check(ServerToCheck::kUpdate);
   // Try harder to connect to the network, esp when not interactive.
   // See comment in libcurl_http_fetcher.cc.
   update_check_fetcher->set_no_network_max_retries(interactive ? 1 : 3);
-  shared_ptr<OmahaRequestAction> update_check_action(
-      new OmahaRequestAction(system_state_,
-                             nullptr,
-                             std::move(update_check_fetcher),
-                             false));
-  shared_ptr<OmahaResponseHandlerAction> response_handler_action(
-      new OmahaResponseHandlerAction(system_state_));
-
-  shared_ptr<OmahaRequestAction> download_started_action(new OmahaRequestAction(
+  auto update_check_action = std::make_unique<OmahaRequestAction>(
+      system_state_, nullptr, std::move(update_check_fetcher), false);
+  auto response_handler_action =
+      std::make_unique<OmahaResponseHandlerAction>(system_state_);
+  auto update_boot_flags_action =
+      std::make_unique<UpdateBootFlagsAction>(system_state_->boot_control());
+  auto download_started_action = std::make_unique<OmahaRequestAction>(
       system_state_,
       new OmahaEvent(OmahaEvent::kTypeUpdateDownloadStarted),
       std::make_unique<LibcurlHttpFetcher>(GetProxyResolver(),
                                            system_state_->hardware()),
-      false));
+      false);
 
   LibcurlHttpFetcher* download_fetcher =
       new LibcurlHttpFetcher(GetProxyResolver(), system_state_->hardware());
   download_fetcher->set_server_to_check(ServerToCheck::kDownload);
   if (interactive)
     download_fetcher->set_max_retry_count(kDownloadMaxRetryCountInteractive);
-  shared_ptr<DownloadAction> download_action(
-      new DownloadAction(prefs_,
-                         system_state_->boot_control(),
-                         system_state_->hardware(),
-                         system_state_,
-                         download_fetcher,  // passes ownership
-                         interactive));
-  shared_ptr<OmahaRequestAction> download_finished_action(
-      new OmahaRequestAction(
-          system_state_,
-          new OmahaEvent(OmahaEvent::kTypeUpdateDownloadFinished),
-          std::make_unique<LibcurlHttpFetcher>(GetProxyResolver(),
-                                               system_state_->hardware()),
-          false));
-  shared_ptr<FilesystemVerifierAction> filesystem_verifier_action(
-      new FilesystemVerifierAction());
-  shared_ptr<OmahaRequestAction> update_complete_action(
-      new OmahaRequestAction(system_state_,
-                             new OmahaEvent(OmahaEvent::kTypeUpdateComplete),
-                             std::make_unique<LibcurlHttpFetcher>(
-                                 GetProxyResolver(), system_state_->hardware()),
-                             false));
-
+  auto download_action =
+      std::make_unique<DownloadAction>(prefs_,
+                                       system_state_->boot_control(),
+                                       system_state_->hardware(),
+                                       system_state_,
+                                       download_fetcher,  // passes ownership
+                                       interactive);
   download_action->set_delegate(this);
-  response_handler_action_ = response_handler_action;
-  download_action_ = download_action;
 
-  actions_.push_back(shared_ptr<AbstractAction>(update_check_action));
-  actions_.push_back(shared_ptr<AbstractAction>(response_handler_action));
-  actions_.push_back(shared_ptr<AbstractAction>(download_started_action));
-  actions_.push_back(shared_ptr<AbstractAction>(download_action));
-  actions_.push_back(shared_ptr<AbstractAction>(download_finished_action));
-  actions_.push_back(shared_ptr<AbstractAction>(filesystem_verifier_action));
+  auto download_finished_action = std::make_unique<OmahaRequestAction>(
+      system_state_,
+      new OmahaEvent(OmahaEvent::kTypeUpdateDownloadFinished),
+      std::make_unique<LibcurlHttpFetcher>(GetProxyResolver(),
+                                           system_state_->hardware()),
+      false);
+  auto filesystem_verifier_action =
+      std::make_unique<FilesystemVerifierAction>();
+  auto update_complete_action = std::make_unique<OmahaRequestAction>(
+      system_state_,
+      new OmahaEvent(OmahaEvent::kTypeUpdateComplete),
+      std::make_unique<LibcurlHttpFetcher>(GetProxyResolver(),
+                                           system_state_->hardware()),
+      false);
+
+  auto postinstall_runner_action = std::make_unique<PostinstallRunnerAction>(
+      system_state_->boot_control(), system_state_->hardware());
+  postinstall_runner_action->set_delegate(this);
 
   // Bond them together. We have to use the leaf-types when calling
   // BondActions().
-  BondActions(update_check_action.get(),
-              response_handler_action.get());
-  BondActions(response_handler_action.get(),
-              download_action.get());
-  BondActions(download_action.get(),
-              filesystem_verifier_action.get());
-  BuildPostInstallActions(filesystem_verifier_action.get());
+  BondActions(update_check_action.get(), response_handler_action.get());
+  BondActions(response_handler_action.get(), download_action.get());
+  BondActions(download_action.get(), filesystem_verifier_action.get());
+  BondActions(filesystem_verifier_action.get(),
+              postinstall_runner_action.get());
 
-  actions_.push_back(shared_ptr<AbstractAction>(update_complete_action));
-
-  // Enqueue the actions
-  for (const shared_ptr<AbstractAction>& action : actions_) {
-    processor_->EnqueueAction(action.get());
-  }
+  processor_->EnqueueAction(std::move(update_check_action));
+  processor_->EnqueueAction(std::move(response_handler_action));
+  processor_->EnqueueAction(std::move(update_boot_flags_action));
+  processor_->EnqueueAction(std::move(download_started_action));
+  processor_->EnqueueAction(std::move(download_action));
+  processor_->EnqueueAction(std::move(download_finished_action));
+  processor_->EnqueueAction(std::move(filesystem_verifier_action));
+  processor_->EnqueueAction(std::move(postinstall_runner_action));
+  processor_->EnqueueAction(std::move(update_complete_action));
 }
 
 bool UpdateAttempter::Rollback(bool powerwash) {
@@ -688,39 +724,32 @@
   }
 
   LOG(INFO) << "Setting rollback options.";
-  InstallPlan install_plan;
-
-  install_plan.target_slot = GetRollbackSlot();
-  install_plan.source_slot = system_state_->boot_control()->GetCurrentSlot();
+  install_plan_.reset(new InstallPlan());
+  install_plan_->target_slot = GetRollbackSlot();
+  install_plan_->source_slot = system_state_->boot_control()->GetCurrentSlot();
 
   TEST_AND_RETURN_FALSE(
-      install_plan.LoadPartitionsFromSlots(system_state_->boot_control()));
-  install_plan.powerwash_required = powerwash;
+      install_plan_->LoadPartitionsFromSlots(system_state_->boot_control()));
+  install_plan_->powerwash_required = powerwash;
 
   LOG(INFO) << "Using this install plan:";
-  install_plan.Dump();
+  install_plan_->Dump();
 
-  shared_ptr<InstallPlanAction> install_plan_action(
-      new InstallPlanAction(install_plan));
-  actions_.push_back(shared_ptr<AbstractAction>(install_plan_action));
-
-  BuildPostInstallActions(install_plan_action.get());
-
-  // Enqueue the actions
-  for (const shared_ptr<AbstractAction>& action : actions_) {
-    processor_->EnqueueAction(action.get());
-  }
+  auto install_plan_action =
+      std::make_unique<InstallPlanAction>(*install_plan_);
+  auto postinstall_runner_action = std::make_unique<PostinstallRunnerAction>(
+      system_state_->boot_control(), system_state_->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));
 
   // Update the payload state for Rollback.
   system_state_->payload_state()->Rollback();
 
   SetStatusAndNotify(UpdateStatus::ATTEMPTING_ROLLBACK);
 
-  // Just in case we didn't update boot flags yet, make sure they're updated
-  // before any update processing starts. This also schedules the start of the
-  // actions we just posted.
-  start_action_processor_ = true;
-  UpdateBootFlags();
+  ScheduleProcessingStart();
   return true;
 }
 
@@ -813,11 +842,13 @@
 }
 
 bool UpdateAttempter::RebootIfNeeded() {
+#ifdef __ANDROID__
   if (status_ != UpdateStatus::UPDATED_NEED_REBOOT) {
     LOG(INFO) << "Reboot requested, but status is "
               << UpdateStatusToString(status_) << ", so not rebooting.";
     return false;
   }
+#endif  // __ANDROID__
 
   if (system_state_->power_manager()->RequestReboot())
     return true;
@@ -879,7 +910,8 @@
            forced_omaha_url_,
            params.target_channel,
            params.target_version_prefix,
-           false,
+           params.rollback_allowed,
+           /*obey_proxies=*/false,
            params.interactive);
     // Always clear the forced app_version and omaha_url after an update attempt
     // so the next update uses the defaults.
@@ -903,11 +935,23 @@
   last_checked_time_ = system_state_->clock()->GetWallclockTime().ToTimeT();
 }
 
+void UpdateAttempter::UpdateRollbackHappened() {
+  DCHECK(system_state_);
+  DCHECK(system_state_->payload_state());
+  DCHECK(policy_provider_);
+  if (system_state_->payload_state()->GetRollbackHappened() &&
+      (policy_provider_->device_policy_is_loaded() ||
+       policy_provider_->IsConsumerDevice())) {
+    // Rollback happened, but we already went through OOBE and policy is
+    // present or it's a consumer device.
+    system_state_->payload_state()->SetRollbackHappened(false);
+  }
+}
+
 // Delegate methods:
 void UpdateAttempter::ProcessingDone(const ActionProcessor* processor,
                                      ErrorCode code) {
   LOG(INFO) << "Processing Done.";
-  actions_.clear();
 
   // Reset cpu shares back to normal.
   cpu_limiter_.StopLimiter();
@@ -915,6 +959,10 @@
   // reset the state that's only valid for a single update pass
   current_update_attempt_flags_ = UpdateAttemptFlags::kNone;
 
+  if (forced_update_pending_callback_.get())
+    // Clear prior interactive requests once the processor is done.
+    forced_update_pending_callback_->Run(false, false);
+
   if (status_ == UpdateStatus::REPORTING_ERROR_EVENT) {
     LOG(INFO) << "Error event sent.";
 
@@ -951,26 +999,32 @@
     // way.
     prefs_->Delete(kPrefsUpdateCheckCount);
     system_state_->payload_state()->SetScatteringWaitPeriod(TimeDelta());
+    system_state_->payload_state()->SetStagingWaitPeriod(TimeDelta());
     prefs_->Delete(kPrefsUpdateFirstSeenAt);
 
     SetStatusAndNotify(UpdateStatus::UPDATED_NEED_REBOOT);
     ScheduleUpdates();
     LOG(INFO) << "Update successfully applied, waiting to reboot.";
 
-    // This pointer is null during rollback operations, and the stats
-    // don't make much sense then anyway.
-    if (response_handler_action_) {
-      const InstallPlan& install_plan =
-          response_handler_action_->install_plan();
-
+    // |install_plan_| is null during rollback operations, and the stats don't
+    // make much sense then anyway.
+    if (install_plan_) {
       // Generate an unique payload identifier.
       string target_version_uid;
-      for (const auto& payload : install_plan.payloads) {
+      for (const auto& payload : install_plan_->payloads) {
         target_version_uid +=
             brillo::data_encoding::Base64Encode(payload.hash) + ":" +
             payload.metadata_signature + ":";
       }
 
+      // If we just downloaded a rollback image, we should preserve this fact
+      // over the following powerwash.
+      if (install_plan_->is_rollback) {
+        system_state_->payload_state()->SetRollbackHappened(true);
+        system_state_->metrics_reporter()->ReportEnterpriseRollbackMetrics(
+            /*success=*/true, install_plan_->version);
+      }
+
       // Expect to reboot into the new version to send the proper metric during
       // next boot.
       system_state_->payload_state()->ExpectRebootInNewVersion(
@@ -979,8 +1033,7 @@
       // If we just finished a rollback, then we expect to have no Omaha
       // response. Otherwise, it's an error.
       if (system_state_->payload_state()->GetRollbackVersion().empty()) {
-        LOG(ERROR) << "Can't send metrics because expected "
-            "response_handler_action_ missing.";
+        LOG(ERROR) << "Can't send metrics because there was no Omaha response";
       }
     }
     return;
@@ -998,9 +1051,11 @@
   // Reset cpu shares back to normal.
   cpu_limiter_.StopLimiter();
   download_progress_ = 0.0;
+  if (forced_update_pending_callback_.get())
+    // Clear prior interactive requests once the processor is done.
+    forced_update_pending_callback_->Run(false, false);
   SetStatusAndNotify(UpdateStatus::IDLE);
   ScheduleUpdates();
-  actions_.clear();
   error_event_.reset(nullptr);
 }
 
@@ -1033,9 +1088,23 @@
         consecutive_failed_update_checks_ = 0;
       }
 
+      const OmahaResponse& omaha_response =
+          omaha_request_action->GetOutputObject();
       // Store the server-dictated poll interval, if any.
       server_dictated_poll_interval_ =
-          std::max(0, omaha_request_action->GetOutputObject().poll_interval);
+          std::max(0, omaha_response.poll_interval);
+
+      // This update is ignored by omaha request action because update over
+      // cellular connection is not allowed. Needs to ask for user's permissions
+      // to update.
+      if (code == ErrorCode::kOmahaUpdateIgnoredOverCellular) {
+        new_version_ = omaha_response.version;
+        new_payload_size_ = 0;
+        for (const auto& package : omaha_response.packages) {
+          new_payload_size_ += package.size;
+        }
+        SetStatusAndNotify(UpdateStatus::NEED_PERMISSION_TO_UPDATE);
+      }
     }
   } else if (type == OmahaResponseHandlerAction::StaticType()) {
     // Depending on the returned error code, note that an update is available.
@@ -1046,13 +1115,15 @@
       // callback is invoked. This avoids notifying the user that a download
       // has started in cases when the server and the client are unable to
       // initiate the download.
-      CHECK(action == response_handler_action_.get());
-      auto plan = response_handler_action_->install_plan();
+      auto omaha_response_handler_action =
+          static_cast<OmahaResponseHandlerAction*>(action);
+      install_plan_.reset(
+          new InstallPlan(omaha_response_handler_action->install_plan()));
       UpdateLastCheckedTime();
-      new_version_ = plan.version;
-      new_system_version_ = plan.system_version;
+      new_version_ = install_plan_->version;
+      new_system_version_ = install_plan_->system_version;
       new_payload_size_ = 0;
-      for (const auto& payload : plan.payloads)
+      for (const auto& payload : install_plan_->payloads)
         new_payload_size_ += payload.size;
       cpu_limiter_.StartLimiter();
       SetStatusAndNotify(UpdateStatus::UPDATE_AVAILABLE);
@@ -1063,9 +1134,23 @@
     // If the current state is at or past the download phase, count the failure
     // in case a switch to full update becomes necessary. Ignore network
     // transfer timeouts and failures.
-    if (status_ >= UpdateStatus::DOWNLOADING &&
-        code != ErrorCode::kDownloadTransferError) {
-      MarkDeltaUpdateFailure();
+    if (code != ErrorCode::kDownloadTransferError) {
+      switch (status_) {
+        case UpdateStatus::IDLE:
+        case UpdateStatus::CHECKING_FOR_UPDATE:
+        case UpdateStatus::UPDATE_AVAILABLE:
+        case UpdateStatus::NEED_PERMISSION_TO_UPDATE:
+          break;
+        case UpdateStatus::DOWNLOADING:
+        case UpdateStatus::VERIFYING:
+        case UpdateStatus::FINALIZING:
+        case UpdateStatus::UPDATED_NEED_REBOOT:
+        case UpdateStatus::REPORTING_ERROR_EVENT:
+        case UpdateStatus::ATTEMPTING_ROLLBACK:
+        case UpdateStatus::DISABLED:
+          MarkDeltaUpdateFailure();
+          break;
+      }
     }
     if (code != ErrorCode::kNoUpdate) {
       // On failure, schedule an error event to be sent to Omaha.
@@ -1183,38 +1268,6 @@
   return true;
 }
 
-void UpdateAttempter::UpdateBootFlags() {
-  if (update_boot_flags_running_) {
-    LOG(INFO) << "Update boot flags running, nothing to do.";
-    return;
-  }
-  if (updated_boot_flags_) {
-    LOG(INFO) << "Already updated boot flags. Skipping.";
-    if (start_action_processor_) {
-      ScheduleProcessingStart();
-    }
-    return;
-  }
-  // This is purely best effort. Failures should be logged by Subprocess. Run
-  // the script asynchronously to avoid blocking the event loop regardless of
-  // the script runtime.
-  update_boot_flags_running_ = true;
-  LOG(INFO) << "Marking booted slot as good.";
-  if (!system_state_->boot_control()->MarkBootSuccessfulAsync(Bind(
-          &UpdateAttempter::CompleteUpdateBootFlags, base::Unretained(this)))) {
-    LOG(ERROR) << "Failed to mark current boot as successful.";
-    CompleteUpdateBootFlags(false);
-  }
-}
-
-void UpdateAttempter::CompleteUpdateBootFlags(bool successful) {
-  update_boot_flags_running_ = false;
-  updated_boot_flags_ = true;
-  if (start_action_processor_) {
-    ScheduleProcessingStart();
-  }
-}
-
 void UpdateAttempter::BroadcastStatus() {
   UpdateEngineStatus broadcast_status;
   // Use common method for generating the current status.
@@ -1232,8 +1285,7 @@
   if (!system_state_->hardware()->IsNormalBootMode())
     flags |= static_cast<uint32_t>(ErrorCode::kDevModeFlag);
 
-  if (response_handler_action_.get() &&
-      response_handler_action_->install_plan().is_resume)
+  if (install_plan_ && install_plan_->is_resume)
     flags |= static_cast<uint32_t>(ErrorCode::kResumedFlag);
 
   if (!system_state_->hardware()->IsOfficialBuild())
@@ -1311,16 +1363,21 @@
   LOG(ERROR) << "Update failed.";
   system_state_->payload_state()->UpdateFailed(error_event_->error_code);
 
+  // Send metrics if it was a rollback.
+  if (install_plan_ && install_plan_->is_rollback) {
+    system_state_->metrics_reporter()->ReportEnterpriseRollbackMetrics(
+        /*success=*/false, install_plan_->version);
+  }
+
   // Send it to Omaha.
   LOG(INFO) << "Reporting the error event";
-  shared_ptr<OmahaRequestAction> error_event_action(
-      new OmahaRequestAction(system_state_,
-                             error_event_.release(),  // Pass ownership.
-                             std::make_unique<LibcurlHttpFetcher>(
-                                 GetProxyResolver(), system_state_->hardware()),
-                             false));
-  actions_.push_back(shared_ptr<AbstractAction>(error_event_action));
-  processor_->EnqueueAction(error_event_action.get());
+  auto error_event_action = std::make_unique<OmahaRequestAction>(
+      system_state_,
+      error_event_.release(),  // Pass ownership.
+      std::make_unique<LibcurlHttpFetcher>(GetProxyResolver(),
+                                           system_state_->hardware()),
+      false);
+  processor_->EnqueueAction(std::move(error_event_action));
   SetStatusAndNotify(UpdateStatus::REPORTING_ERROR_EVENT);
   processor_->StartProcessing();
   return true;
@@ -1328,7 +1385,6 @@
 
 void UpdateAttempter::ScheduleProcessingStart() {
   LOG(INFO) << "Scheduling an action processor start.";
-  start_action_processor_ = false;
   MessageLoop::current()->PostTask(
       FROM_HERE,
       Bind([](ActionProcessor* processor) { processor->StartProcessing(); },
@@ -1358,15 +1414,14 @@
 
 void UpdateAttempter::PingOmaha() {
   if (!processor_->IsRunning()) {
-    shared_ptr<OmahaRequestAction> ping_action(new OmahaRequestAction(
+    auto ping_action = std::make_unique<OmahaRequestAction>(
         system_state_,
         nullptr,
         std::make_unique<LibcurlHttpFetcher>(GetProxyResolver(),
                                              system_state_->hardware()),
-        true));
-    actions_.push_back(shared_ptr<OmahaRequestAction>(ping_action));
+        true);
     processor_->set_delegate(nullptr);
-    processor_->EnqueueAction(ping_action.get());
+    processor_->EnqueueAction(std::move(ping_action));
     // Call StartProcessing() synchronously here to avoid any race conditions
     // caused by multiple outstanding ping Omaha requests.  If we call
     // StartProcessing() asynchronously, the device can be suspended before we
diff --git a/update_attempter.h b/update_attempter.h
index 82fe4c0..108a6c6 100644
--- a/update_attempter.h
+++ b/update_attempter.h
@@ -44,6 +44,7 @@
 #include "update_engine/service_observer_interface.h"
 #include "update_engine/system_state.h"
 #include "update_engine/update_manager/policy.h"
+#include "update_engine/update_manager/staging_utils.h"
 #include "update_engine/update_manager/update_manager.h"
 
 namespace policy {
@@ -83,6 +84,7 @@
                       const std::string& omaha_url,
                       const std::string& target_channel,
                       const std::string& target_version_prefix,
+                      bool rollback_allowed,
                       bool obey_proxies,
                       bool interactive);
 
@@ -266,8 +268,17 @@
   FRIEND_TEST(UpdateAttempterTest, MarkDeltaUpdateFailureTest);
   FRIEND_TEST(UpdateAttempterTest, PingOmahaTest);
   FRIEND_TEST(UpdateAttempterTest, ReportDailyMetrics);
+  FRIEND_TEST(UpdateAttempterTest, RollbackNotAllowed);
+  FRIEND_TEST(UpdateAttempterTest, RollbackAllowed);
+  FRIEND_TEST(UpdateAttempterTest, RollbackAllowedSetAndReset);
+  FRIEND_TEST(UpdateAttempterTest, RollbackMetricsNotRollbackFailure);
+  FRIEND_TEST(UpdateAttempterTest, RollbackMetricsNotRollbackSuccess);
+  FRIEND_TEST(UpdateAttempterTest, RollbackMetricsRollbackFailure);
+  FRIEND_TEST(UpdateAttempterTest, RollbackMetricsRollbackSuccess);
   FRIEND_TEST(UpdateAttempterTest, ScheduleErrorEventActionNoEventTest);
   FRIEND_TEST(UpdateAttempterTest, ScheduleErrorEventActionTest);
+  FRIEND_TEST(UpdateAttempterTest, SetRollbackHappenedNotRollback);
+  FRIEND_TEST(UpdateAttempterTest, SetRollbackHappenedRollback);
   FRIEND_TEST(UpdateAttempterTest, TargetVersionPrefixSetAndReset);
   FRIEND_TEST(UpdateAttempterTest, UpdateAttemptFlagsCachedAtUpdateStart);
   FRIEND_TEST(UpdateAttempterTest, UpdateDeferredByPolicyTest);
@@ -337,6 +348,7 @@
                              const std::string& omaha_url,
                              const std::string& target_channel,
                              const std::string& target_version_prefix,
+                             bool rollback_allowed,
                              bool obey_proxies,
                              bool interactive);
 
@@ -351,12 +363,6 @@
   // scatter factor value specified from policy.
   void GenerateNewWaitingPeriod();
 
-  // Helper method of Update() and Rollback() to construct the sequence of
-  // actions to be performed for the postinstall.
-  // |previous_action| is the previous action to get
-  // bonded with the install_plan that gets passed to postinstall.
-  void BuildPostInstallActions(InstallPlanAction* previous_action);
-
   // Helper method of Update() to construct the sequence of actions to
   // be performed for an update check. Please refer to
   // Update() method for the meaning of the parameters.
@@ -398,15 +404,20 @@
   // Updates the time an update was last attempted to the current time.
   void UpdateLastCheckedTime();
 
+  // Checks whether we need to clear the rollback-happened preference after
+  // policy is available again.
+  void UpdateRollbackHappened();
+
   // Returns whether an update is currently running or scheduled.
   bool IsUpdateRunningOrScheduled();
 
+  void CalculateStagingParams(bool interactive);
+
   // Last status notification timestamp used for throttling. Use monotonic
   // TimeTicks to ensure that notifications are sent even if the system clock is
   // set back in the middle of an update.
   base::TimeTicks last_notify_time_;
 
-  std::vector<std::shared_ptr<AbstractAction>> actions_;
   std::unique_ptr<ActionProcessor> processor_;
 
   // External state of the system outside the update_engine process
@@ -419,11 +430,8 @@
   // The list of services observing changes in the updater.
   std::set<ServiceObserverInterface*> service_observers_;
 
-  // Pointer to the OmahaResponseHandlerAction in the actions_ vector.
-  std::shared_ptr<OmahaResponseHandlerAction> response_handler_action_;
-
-  // Pointer to the DownloadAction in the actions_ vector.
-  std::shared_ptr<DownloadAction> download_action_;
+  // The install plan.
+  std::unique_ptr<InstallPlan> install_plan_;
 
   // Pointer to the preferences store interface. This is just a cached
   // copy of system_state->prefs() because it's used in many methods and
@@ -475,20 +483,6 @@
   ChromeBrowserProxyResolver chrome_proxy_resolver_;
 #endif  // USE_CHROME_NETWORK_PROXY
 
-  // Originally, both of these flags are false. Once UpdateBootFlags is called,
-  // |update_boot_flags_running_| is set to true. As soon as UpdateBootFlags
-  // completes its asynchronous run, |update_boot_flags_running_| is reset to
-  // false and |updated_boot_flags_| is set to true. From that point on there
-  // will be no more changes to these flags.
-  //
-  // True if UpdateBootFlags has completed.
-  bool updated_boot_flags_ = false;
-  // True if UpdateBootFlags is running.
-  bool update_boot_flags_running_ = false;
-
-  // True if the action processor needs to be started by the boot flag updater.
-  bool start_action_processor_ = false;
-
   // Used for fetching information about the device policy.
   std::unique_ptr<policy::PolicyProvider> policy_provider_;
 
@@ -519,6 +513,10 @@
   std::string forced_app_version_;
   std::string forced_omaha_url_;
 
+  // If this is not TimeDelta(), then that means staging is turned on.
+  base::TimeDelta staging_wait_time_;
+  chromeos_update_manager::StagingSchedule staging_schedule_;
+
   DISALLOW_COPY_AND_ASSIGN(UpdateAttempter);
 };
 
diff --git a/update_attempter_android.cc b/update_attempter_android.cc
index 3ddfd85..9443869 100644
--- a/update_attempter_android.cc
+++ b/update_attempter_android.cc
@@ -47,6 +47,7 @@
 #include "update_engine/payload_consumer/payload_constants.h"
 #include "update_engine/payload_consumer/payload_metadata.h"
 #include "update_engine/payload_consumer/postinstall_runner_action.h"
+#include "update_engine/update_boot_flags_action.h"
 #include "update_engine/update_status_utils.h"
 
 #ifndef _UE_SIDELOAD
@@ -246,23 +247,34 @@
   LOG(INFO) << "Using this install plan:";
   install_plan_.Dump();
 
-  BuildUpdateActions(payload_url);
+  HttpFetcher* fetcher = nullptr;
+  if (FileFetcher::SupportedUrl(payload_url)) {
+    DLOG(INFO) << "Using FileFetcher for file URL.";
+    fetcher = new FileFetcher();
+  } else {
+#ifdef _UE_SIDELOAD
+    LOG(FATAL) << "Unsupported sideload URI: " << payload_url;
+#else
+    LibcurlHttpFetcher* libcurl_fetcher =
+        new LibcurlHttpFetcher(&proxy_resolver_, hardware_);
+    libcurl_fetcher->set_server_to_check(ServerToCheck::kDownload);
+    fetcher = libcurl_fetcher;
+#endif  // _UE_SIDELOAD
+  }
   // Setup extra headers.
-  HttpFetcher* fetcher = download_action_->http_fetcher();
   if (!headers[kPayloadPropertyAuthorization].empty())
     fetcher->SetHeader("Authorization", headers[kPayloadPropertyAuthorization]);
   if (!headers[kPayloadPropertyUserAgent].empty())
     fetcher->SetHeader("User-Agent", headers[kPayloadPropertyUserAgent]);
 
-  SetStatusAndNotify(UpdateStatus::UPDATE_AVAILABLE);
+  BuildUpdateActions(fetcher);
 
-  // Just in case we didn't update boot flags yet, make sure they're updated
-  // before any update processing starts. This will start the update process.
-  UpdateBootFlags();
+  SetStatusAndNotify(UpdateStatus::UPDATE_AVAILABLE);
 
   UpdatePrefsOnUpdateStart(install_plan_.is_resume);
   // TODO(xunchang) report the metrics for unresumable updates
 
+  ScheduleProcessingStart();
   return true;
 }
 
@@ -532,27 +544,6 @@
   }
 }
 
-void UpdateAttempterAndroid::UpdateBootFlags() {
-  if (updated_boot_flags_) {
-    LOG(INFO) << "Already updated boot flags. Skipping.";
-    CompleteUpdateBootFlags(true);
-    return;
-  }
-  // This is purely best effort.
-  LOG(INFO) << "Marking booted slot as good.";
-  if (!boot_control_->MarkBootSuccessfulAsync(
-          Bind(&UpdateAttempterAndroid::CompleteUpdateBootFlags,
-               base::Unretained(this)))) {
-    LOG(ERROR) << "Failed to mark current boot as successful.";
-    CompleteUpdateBootFlags(false);
-  }
-}
-
-void UpdateAttempterAndroid::CompleteUpdateBootFlags(bool successful) {
-  updated_boot_flags_ = true;
-  ScheduleProcessingStart();
-}
-
 void UpdateAttempterAndroid::ScheduleProcessingStart() {
   LOG(INFO) << "Scheduling an action processor start.";
   brillo::MessageLoop::current()->PostTask(
@@ -568,7 +559,6 @@
   }
 
   download_progress_ = 0;
-  actions_.clear();
   UpdateStatus new_status =
       (error_code == ErrorCode::kSuccess ? UpdateStatus::UPDATED_NEED_REBOOT
                                          : UpdateStatus::IDLE);
@@ -606,51 +596,29 @@
   last_notify_time_ = TimeTicks::Now();
 }
 
-void UpdateAttempterAndroid::BuildUpdateActions(const string& url) {
+void UpdateAttempterAndroid::BuildUpdateActions(HttpFetcher* fetcher) {
   CHECK(!processor_->IsRunning());
   processor_->set_delegate(this);
 
   // Actions:
-  shared_ptr<InstallPlanAction> install_plan_action(
-      new InstallPlanAction(install_plan_));
-
-  HttpFetcher* download_fetcher = nullptr;
-  if (FileFetcher::SupportedUrl(url)) {
-    DLOG(INFO) << "Using FileFetcher for file URL.";
-    download_fetcher = new FileFetcher();
-  } else {
-#ifdef _UE_SIDELOAD
-    LOG(FATAL) << "Unsupported sideload URI: " << url;
-#else
-    LibcurlHttpFetcher* libcurl_fetcher =
-        new LibcurlHttpFetcher(&proxy_resolver_, hardware_);
-    libcurl_fetcher->set_server_to_check(ServerToCheck::kDownload);
-    download_fetcher = libcurl_fetcher;
-#endif  // _UE_SIDELOAD
-  }
-  shared_ptr<DownloadAction> download_action(
-      new DownloadAction(prefs_,
-                         boot_control_,
-                         hardware_,
-                         nullptr,           // system_state, not used.
-                         download_fetcher,  // passes ownership
-                         true /* interactive */));
-  shared_ptr<FilesystemVerifierAction> filesystem_verifier_action(
-      new FilesystemVerifierAction());
-
-  shared_ptr<PostinstallRunnerAction> postinstall_runner_action(
-      new PostinstallRunnerAction(boot_control_, hardware_));
-
+  auto update_boot_flags_action =
+      std::make_unique<UpdateBootFlagsAction>(boot_control_);
+  auto install_plan_action = std::make_unique<InstallPlanAction>(install_plan_);
+  auto download_action =
+      std::make_unique<DownloadAction>(prefs_,
+                                       boot_control_,
+                                       hardware_,
+                                       nullptr,  // system_state, not used.
+                                       fetcher,  // passes ownership
+                                       true /* interactive */);
   download_action->set_delegate(this);
   download_action->set_base_offset(base_offset_);
-  download_action_ = download_action;
+  auto filesystem_verifier_action =
+      std::make_unique<FilesystemVerifierAction>();
+  auto postinstall_runner_action =
+      std::make_unique<PostinstallRunnerAction>(boot_control_, hardware_);
   postinstall_runner_action->set_delegate(this);
 
-  actions_.push_back(shared_ptr<AbstractAction>(install_plan_action));
-  actions_.push_back(shared_ptr<AbstractAction>(download_action));
-  actions_.push_back(shared_ptr<AbstractAction>(filesystem_verifier_action));
-  actions_.push_back(shared_ptr<AbstractAction>(postinstall_runner_action));
-
   // Bond them together. We have to use the leaf-types when calling
   // BondActions().
   BondActions(install_plan_action.get(), download_action.get());
@@ -658,9 +626,11 @@
   BondActions(filesystem_verifier_action.get(),
               postinstall_runner_action.get());
 
-  // Enqueue the actions.
-  for (const shared_ptr<AbstractAction>& action : actions_)
-    processor_->EnqueueAction(action.get());
+  processor_->EnqueueAction(std::move(update_boot_flags_action));
+  processor_->EnqueueAction(std::move(install_plan_action));
+  processor_->EnqueueAction(std::move(download_action));
+  processor_->EnqueueAction(std::move(filesystem_verifier_action));
+  processor_->EnqueueAction(std::move(postinstall_runner_action));
 }
 
 bool UpdateAttempterAndroid::WriteUpdateCompletedMarker() {
diff --git a/update_attempter_android.h b/update_attempter_android.h
index a8f388c..3faeac9 100644
--- a/update_attempter_android.h
+++ b/update_attempter_android.h
@@ -114,8 +114,9 @@
   void SetStatusAndNotify(UpdateStatus status);
 
   // Helper method to construct the sequence of actions to be performed for
-  // applying an update from the given |url|.
-  void BuildUpdateActions(const std::string& url);
+  // applying an update using a given HttpFetcher. The ownership of |fetcher| is
+  // passed to this function.
+  void BuildUpdateActions(HttpFetcher* fetcher);
 
   // Writes to the processing completed marker. Does nothing if
   // |update_completed_marker_| is empty.
@@ -171,14 +172,9 @@
   // set back in the middle of an update.
   base::TimeTicks last_notify_time_;
 
-  // The list of actions and action processor that runs them asynchronously.
-  // Only used when |ongoing_update_| is true.
-  std::vector<std::shared_ptr<AbstractAction>> actions_;
+  // The processor for running Actions.
   std::unique_ptr<ActionProcessor> processor_;
 
-  // Pointer to the DownloadAction in the actions_ vector.
-  std::shared_ptr<DownloadAction> download_action_;
-
   // The InstallPlan used during the ongoing update.
   InstallPlan install_plan_;
 
@@ -195,10 +191,6 @@
   // Helper class to select the network to use during the update.
   std::unique_ptr<NetworkSelectorInterface> network_selector_;
 
-  // Whether we have marked the current slot as good. This step is required
-  // before applying an update to the other slot.
-  bool updated_boot_flags_ = false;
-
   std::unique_ptr<ClockInterface> clock_;
 
   std::unique_ptr<MetricsReporterInterface> metrics_reporter_;
diff --git a/update_attempter_unittest.cc b/update_attempter_unittest.cc
index 4677d31..fb9f7bc 100644
--- a/update_attempter_unittest.cc
+++ b/update_attempter_unittest.cc
@@ -28,6 +28,7 @@
 #include <gtest/gtest.h>
 #include <policy/libpolicy.h>
 #include <policy/mock_device_policy.h>
+#include <policy/mock_libpolicy.h>
 
 #include "update_engine/common/fake_clock.h"
 #include "update_engine/common/fake_prefs.h"
@@ -47,11 +48,14 @@
 #include "update_engine/payload_consumer/install_plan.h"
 #include "update_engine/payload_consumer/payload_constants.h"
 #include "update_engine/payload_consumer/postinstall_runner_action.h"
+#include "update_engine/update_boot_flags_action.h"
 
 using base::Time;
 using base::TimeDelta;
 using chromeos_update_manager::EvalStatus;
+using chromeos_update_manager::StagingSchedule;
 using chromeos_update_manager::UpdateCheckParams;
+using policy::DevicePolicy;
 using std::string;
 using std::unique_ptr;
 using testing::_;
@@ -60,9 +64,11 @@
 using testing::InSequence;
 using testing::Ne;
 using testing::NiceMock;
+using testing::Pointee;
 using testing::Property;
 using testing::Return;
 using testing::ReturnPointee;
+using testing::ReturnRef;
 using testing::SaveArg;
 using testing::SetArgPointee;
 using update_engine::UpdateAttemptFlags;
@@ -71,6 +77,8 @@
 
 namespace chromeos_update_engine {
 
+const char kRollbackVersion[] = "10575.39.2";
+
 // Test a subclass rather than the main class directly so that we can mock out
 // methods within the class. There're explicit unit tests for the mocked out
 // methods.
@@ -165,6 +173,15 @@
   void P2PEnabledInteractiveStart();
   void P2PEnabledStartingFailsStart();
   void P2PEnabledHousekeepingFailsStart();
+  void ResetRollbackHappenedStart(bool is_consumer,
+                                  bool is_policy_available,
+                                  bool expected_reset);
+  // Staging related callbacks.
+  void SetUpStagingTest(const StagingSchedule& schedule, FakePrefs* prefs);
+  void CheckStagingOff();
+  void StagingSetsPrefsAndTurnsOffScatteringStart();
+  void StagingOffIfInteractiveStart();
+  void StagingOffIfOobeStart();
 
   bool actual_using_p2p_for_downloading() {
     return actual_using_p2p_for_downloading_;
@@ -418,8 +435,8 @@
 
 TEST_F(UpdateAttempterTest, ScheduleErrorEventActionTest) {
   EXPECT_CALL(*processor_,
-              EnqueueAction(Property(&AbstractAction::Type,
-                                     OmahaRequestAction::StaticType())));
+              EnqueueAction(Pointee(Property(
+                  &AbstractAction::Type, OmahaRequestAction::StaticType()))));
   EXPECT_CALL(*processor_, StartProcessing());
   ErrorCode err = ErrorCode::kError;
   EXPECT_CALL(*fake_system_state_.mock_payload_state(), UpdateFailed(err));
@@ -433,15 +450,15 @@
 namespace {
 // Actions that will be built as part of an update check.
 const string kUpdateActionTypes[] = {  // NOLINT(runtime/string)
-  OmahaRequestAction::StaticType(),
-  OmahaResponseHandlerAction::StaticType(),
-  OmahaRequestAction::StaticType(),
-  DownloadAction::StaticType(),
-  OmahaRequestAction::StaticType(),
-  FilesystemVerifierAction::StaticType(),
-  PostinstallRunnerAction::StaticType(),
-  OmahaRequestAction::StaticType()
-};
+    OmahaRequestAction::StaticType(),
+    OmahaResponseHandlerAction::StaticType(),
+    UpdateBootFlagsAction::StaticType(),
+    OmahaRequestAction::StaticType(),
+    DownloadAction::StaticType(),
+    OmahaRequestAction::StaticType(),
+    FilesystemVerifierAction::StaticType(),
+    PostinstallRunnerAction::StaticType(),
+    OmahaRequestAction::StaticType()};
 
 // Actions that will be built as part of a user-initiated rollback.
 const string kRollbackActionTypes[] = {  // NOLINT(runtime/string)
@@ -449,6 +466,9 @@
   PostinstallRunnerAction::StaticType(),
 };
 
+const StagingSchedule kValidStagingSchedule = {
+    {4, 10}, {10, 40}, {19, 70}, {26, 100}};
+
 }  // namespace
 
 void UpdateAttempterTest::UpdateTestStart() {
@@ -466,13 +486,13 @@
     InSequence s;
     for (size_t i = 0; i < arraysize(kUpdateActionTypes); ++i) {
       EXPECT_CALL(*processor_,
-                  EnqueueAction(Property(&AbstractAction::Type,
-                                         kUpdateActionTypes[i])));
+                  EnqueueAction(Pointee(
+                      Property(&AbstractAction::Type, kUpdateActionTypes[i]))));
     }
     EXPECT_CALL(*processor_, StartProcessing());
   }
 
-  attempter_.Update("", "", "", "", false, false);
+  attempter_.Update("", "", "", "", false, false, false);
   loop_.PostTask(FROM_HERE,
                  base::Bind(&UpdateAttempterTest::UpdateTestVerify,
                             base::Unretained(this)));
@@ -481,17 +501,6 @@
 void UpdateAttempterTest::UpdateTestVerify() {
   EXPECT_EQ(0, attempter_.http_response_code());
   EXPECT_EQ(&attempter_, processor_->delegate());
-  EXPECT_EQ(arraysize(kUpdateActionTypes), attempter_.actions_.size());
-  for (size_t i = 0; i < arraysize(kUpdateActionTypes); ++i) {
-    EXPECT_EQ(kUpdateActionTypes[i], attempter_.actions_[i]->Type());
-  }
-  EXPECT_EQ(attempter_.response_handler_action_.get(),
-            attempter_.actions_[1].get());
-  AbstractAction* action_3 = attempter_.actions_[3].get();
-  ASSERT_NE(nullptr, action_3);
-  ASSERT_EQ(DownloadAction::StaticType(), action_3->Type());
-  DownloadAction* download_action = static_cast<DownloadAction*>(action_3);
-  EXPECT_EQ(&attempter_, download_action->delegate());
   EXPECT_EQ(UpdateStatus::CHECKING_FOR_UPDATE, attempter_.status());
   loop_.BreakLoop();
 }
@@ -537,8 +546,8 @@
     InSequence s;
     for (size_t i = 0; i < arraysize(kRollbackActionTypes); ++i) {
       EXPECT_CALL(*processor_,
-                  EnqueueAction(Property(&AbstractAction::Type,
-                                         kRollbackActionTypes[i])));
+                  EnqueueAction(Pointee(Property(&AbstractAction::Type,
+                                                 kRollbackActionTypes[i]))));
     }
     EXPECT_CALL(*processor_, StartProcessing());
 
@@ -555,19 +564,9 @@
 void UpdateAttempterTest::RollbackTestVerify() {
   // Verifies the actions that were enqueued.
   EXPECT_EQ(&attempter_, processor_->delegate());
-  EXPECT_EQ(arraysize(kRollbackActionTypes), attempter_.actions_.size());
-  for (size_t i = 0; i < arraysize(kRollbackActionTypes); ++i) {
-    EXPECT_EQ(kRollbackActionTypes[i], attempter_.actions_[i]->Type());
-  }
   EXPECT_EQ(UpdateStatus::ATTEMPTING_ROLLBACK, attempter_.status());
-  AbstractAction* action_0 = attempter_.actions_[0].get();
-  ASSERT_NE(nullptr, action_0);
-  ASSERT_EQ(InstallPlanAction::StaticType(), action_0->Type());
-  InstallPlanAction* install_plan_action =
-      static_cast<InstallPlanAction*>(action_0);
-  InstallPlan* install_plan = install_plan_action->install_plan();
-  EXPECT_EQ(0U, install_plan->partitions.size());
-  EXPECT_EQ(install_plan->powerwash_required, true);
+  EXPECT_EQ(0U, attempter_.install_plan_->partitions.size());
+  EXPECT_EQ(attempter_.install_plan_->powerwash_required, true);
   loop_.BreakLoop();
 }
 
@@ -602,8 +601,8 @@
 
 void UpdateAttempterTest::PingOmahaTestStart() {
   EXPECT_CALL(*processor_,
-              EnqueueAction(Property(&AbstractAction::Type,
-                                     OmahaRequestAction::StaticType())));
+              EnqueueAction(Pointee(Property(
+                  &AbstractAction::Type, OmahaRequestAction::StaticType()))));
   EXPECT_CALL(*processor_, StartProcessing());
   attempter_.PingOmaha();
   ScheduleQuitMainLoop();
@@ -637,10 +636,8 @@
 }
 
 TEST_F(UpdateAttempterTest, CreatePendingErrorEventResumedTest) {
-  OmahaResponseHandlerAction *response_action =
-      new OmahaResponseHandlerAction(&fake_system_state_);
-  response_action->install_plan_.is_resume = true;
-  attempter_.response_handler_action_.reset(response_action);
+  attempter_.install_plan_.reset(new InstallPlan);
+  attempter_.install_plan_->is_resume = true;
   MockAction action;
   const ErrorCode kCode = ErrorCode::kInstallDeviceOpenError;
   attempter_.CreatePendingErrorEvent(&action, kCode);
@@ -694,7 +691,7 @@
   fake_system_state_.set_p2p_manager(&mock_p2p_manager);
   mock_p2p_manager.fake().SetP2PEnabled(false);
   EXPECT_CALL(mock_p2p_manager, PerformHousekeeping()).Times(0);
-  attempter_.Update("", "", "", "", false, false);
+  attempter_.Update("", "", "", "", false, false, false);
   EXPECT_FALSE(actual_using_p2p_for_downloading_);
   EXPECT_FALSE(actual_using_p2p_for_sharing());
   ScheduleQuitMainLoop();
@@ -716,7 +713,7 @@
   mock_p2p_manager.fake().SetEnsureP2PRunningResult(false);
   mock_p2p_manager.fake().SetPerformHousekeepingResult(false);
   EXPECT_CALL(mock_p2p_manager, PerformHousekeeping()).Times(0);
-  attempter_.Update("", "", "", "", false, false);
+  attempter_.Update("", "", "", "", false, false, false);
   EXPECT_FALSE(actual_using_p2p_for_downloading());
   EXPECT_FALSE(actual_using_p2p_for_sharing());
   ScheduleQuitMainLoop();
@@ -739,7 +736,7 @@
   mock_p2p_manager.fake().SetEnsureP2PRunningResult(true);
   mock_p2p_manager.fake().SetPerformHousekeepingResult(false);
   EXPECT_CALL(mock_p2p_manager, PerformHousekeeping());
-  attempter_.Update("", "", "", "", false, false);
+  attempter_.Update("", "", "", "", false, false, false);
   EXPECT_FALSE(actual_using_p2p_for_downloading());
   EXPECT_FALSE(actual_using_p2p_for_sharing());
   ScheduleQuitMainLoop();
@@ -761,7 +758,7 @@
   mock_p2p_manager.fake().SetEnsureP2PRunningResult(true);
   mock_p2p_manager.fake().SetPerformHousekeepingResult(true);
   EXPECT_CALL(mock_p2p_manager, PerformHousekeeping());
-  attempter_.Update("", "", "", "", false, false);
+  attempter_.Update("", "", "", "", false, false, false);
   EXPECT_TRUE(actual_using_p2p_for_downloading());
   EXPECT_TRUE(actual_using_p2p_for_sharing());
   ScheduleQuitMainLoop();
@@ -784,7 +781,13 @@
   mock_p2p_manager.fake().SetEnsureP2PRunningResult(true);
   mock_p2p_manager.fake().SetPerformHousekeepingResult(true);
   EXPECT_CALL(mock_p2p_manager, PerformHousekeeping());
-  attempter_.Update("", "", "", "", false, true /* interactive */);
+  attempter_.Update("",
+                    "",
+                    "",
+                    "",
+                    false,
+                    false,
+                    /*interactive=*/true);
   EXPECT_FALSE(actual_using_p2p_for_downloading());
   EXPECT_TRUE(actual_using_p2p_for_sharing());
   ScheduleQuitMainLoop();
@@ -815,7 +818,7 @@
   attempter_.policy_provider_.reset(
       new policy::PolicyProvider(std::move(device_policy)));
 
-  attempter_.Update("", "", "", "", false, false);
+  attempter_.Update("", "", "", "", false, false, false);
   EXPECT_EQ(scatter_factor_in_seconds, attempter_.scatter_factor_.InSeconds());
 
   ScheduleQuitMainLoop();
@@ -854,7 +857,7 @@
   attempter_.policy_provider_.reset(
       new policy::PolicyProvider(std::move(device_policy)));
 
-  attempter_.Update("", "", "", "", false, false);
+  attempter_.Update("", "", "", "", false, false, false);
   EXPECT_EQ(scatter_factor_in_seconds, attempter_.scatter_factor_.InSeconds());
 
   // Make sure the file still exists.
@@ -870,7 +873,7 @@
   // However, if the count is already 0, it's not decremented. Test that.
   initial_value = 0;
   EXPECT_TRUE(fake_prefs.SetInt64(kPrefsUpdateCheckCount, initial_value));
-  attempter_.Update("", "", "", "", false, false);
+  attempter_.Update("", "", "", "", false, false, false);
   EXPECT_TRUE(fake_prefs.Exists(kPrefsUpdateCheckCount));
   EXPECT_TRUE(fake_prefs.GetInt64(kPrefsUpdateCheckCount, &new_value));
   EXPECT_EQ(initial_value, new_value);
@@ -895,7 +898,8 @@
   fake_system_state_.fake_hardware()->SetIsOOBEComplete(Time::UnixEpoch());
   fake_system_state_.set_prefs(&fake_prefs);
 
-  EXPECT_TRUE(fake_prefs.SetInt64(kPrefsWallClockWaitPeriod, initial_value));
+  EXPECT_TRUE(
+      fake_prefs.SetInt64(kPrefsWallClockScatteringWaitPeriod, initial_value));
   EXPECT_TRUE(fake_prefs.SetInt64(kPrefsUpdateCheckCount, initial_value));
 
   // make sure scatter_factor is non-zero as scattering is disabled
@@ -915,14 +919,20 @@
       new policy::PolicyProvider(std::move(device_policy)));
 
   // Trigger an interactive check so we can test that scattering is disabled.
-  attempter_.Update("", "", "", "", false, true);
+  attempter_.Update("",
+                    "",
+                    "",
+                    "",
+                    false,
+                    false,
+                    /*interactive=*/true);
   EXPECT_EQ(scatter_factor_in_seconds, attempter_.scatter_factor_.InSeconds());
 
   // Make sure scattering is disabled for manual (i.e. user initiated) update
   // checks and all artifacts are removed.
   EXPECT_FALSE(
       attempter_.omaha_request_params_->wall_clock_based_wait_enabled());
-  EXPECT_FALSE(fake_prefs.Exists(kPrefsWallClockWaitPeriod));
+  EXPECT_FALSE(fake_prefs.Exists(kPrefsWallClockScatteringWaitPeriod));
   EXPECT_EQ(0, attempter_.omaha_request_params_->waiting_period().InSeconds());
   EXPECT_FALSE(
       attempter_.omaha_request_params_->update_check_count_wait_enabled());
@@ -931,6 +941,125 @@
   ScheduleQuitMainLoop();
 }
 
+void UpdateAttempterTest::SetUpStagingTest(const StagingSchedule& schedule,
+                                           FakePrefs* prefs) {
+  attempter_.prefs_ = prefs;
+  fake_system_state_.set_prefs(prefs);
+
+  int64_t initial_value = 8;
+  EXPECT_TRUE(
+      prefs->SetInt64(kPrefsWallClockScatteringWaitPeriod, initial_value));
+  EXPECT_TRUE(prefs->SetInt64(kPrefsUpdateCheckCount, initial_value));
+  attempter_.scatter_factor_ = TimeDelta::FromSeconds(20);
+
+  auto device_policy = std::make_unique<policy::MockDevicePolicy>();
+  EXPECT_CALL(*device_policy, LoadPolicy()).WillRepeatedly(Return(true));
+  fake_system_state_.set_device_policy(device_policy.get());
+  EXPECT_CALL(*device_policy, GetDeviceUpdateStagingSchedule(_))
+      .WillRepeatedly(DoAll(SetArgPointee<0>(schedule), Return(true)));
+
+  attempter_.policy_provider_.reset(
+      new policy::PolicyProvider(std::move(device_policy)));
+}
+
+TEST_F(UpdateAttempterTest, StagingSetsPrefsAndTurnsOffScattering) {
+  loop_.PostTask(
+      FROM_HERE,
+      base::Bind(
+          &UpdateAttempterTest::StagingSetsPrefsAndTurnsOffScatteringStart,
+          base::Unretained(this)));
+  loop_.Run();
+}
+
+void UpdateAttempterTest::StagingSetsPrefsAndTurnsOffScatteringStart() {
+  // Tests that staging sets its prefs properly and turns off scattering.
+  fake_system_state_.fake_hardware()->SetIsOOBEComplete(Time::UnixEpoch());
+  FakePrefs fake_prefs;
+  SetUpStagingTest(kValidStagingSchedule, &fake_prefs);
+
+  attempter_.Update("", "", "", "", false, false, false);
+  // Check that prefs have the correct values.
+  int64_t update_count;
+  EXPECT_TRUE(fake_prefs.GetInt64(kPrefsUpdateCheckCount, &update_count));
+  int64_t waiting_time_days;
+  EXPECT_TRUE(fake_prefs.GetInt64(kPrefsWallClockStagingWaitPeriod,
+                                  &waiting_time_days));
+  EXPECT_GT(waiting_time_days, 0);
+  // Update count should have been decremented.
+  EXPECT_EQ(7, update_count);
+  // Check that Omaha parameters were updated correctly.
+  EXPECT_TRUE(
+      attempter_.omaha_request_params_->update_check_count_wait_enabled());
+  EXPECT_TRUE(
+      attempter_.omaha_request_params_->wall_clock_based_wait_enabled());
+  EXPECT_EQ(waiting_time_days,
+            attempter_.omaha_request_params_->waiting_period().InDays());
+  // Check class variables.
+  EXPECT_EQ(waiting_time_days, attempter_.staging_wait_time_.InDays());
+  EXPECT_EQ(kValidStagingSchedule, attempter_.staging_schedule_);
+  // Check that scattering is turned off
+  EXPECT_EQ(0, attempter_.scatter_factor_.InSeconds());
+  EXPECT_FALSE(fake_prefs.Exists(kPrefsWallClockScatteringWaitPeriod));
+
+  ScheduleQuitMainLoop();
+}
+
+void UpdateAttempterTest::CheckStagingOff() {
+  // Check that all prefs were removed.
+  EXPECT_FALSE(attempter_.prefs_->Exists(kPrefsUpdateCheckCount));
+  EXPECT_FALSE(attempter_.prefs_->Exists(kPrefsWallClockScatteringWaitPeriod));
+  EXPECT_FALSE(attempter_.prefs_->Exists(kPrefsWallClockStagingWaitPeriod));
+  // Check that the Omaha parameters have the correct value.
+  EXPECT_EQ(0, attempter_.omaha_request_params_->waiting_period().InDays());
+  EXPECT_EQ(attempter_.omaha_request_params_->waiting_period(),
+            attempter_.staging_wait_time_);
+  EXPECT_FALSE(
+      attempter_.omaha_request_params_->update_check_count_wait_enabled());
+  EXPECT_FALSE(
+      attempter_.omaha_request_params_->wall_clock_based_wait_enabled());
+  // Check that scattering is turned off too.
+  EXPECT_EQ(0, attempter_.scatter_factor_.InSeconds());
+}
+
+TEST_F(UpdateAttempterTest, StagingOffIfInteractive) {
+  loop_.PostTask(FROM_HERE,
+                 base::Bind(&UpdateAttempterTest::StagingOffIfInteractiveStart,
+                            base::Unretained(this)));
+  loop_.Run();
+}
+
+void UpdateAttempterTest::StagingOffIfInteractiveStart() {
+  // Tests that staging is turned off when an interactive update is requested.
+  fake_system_state_.fake_hardware()->SetIsOOBEComplete(Time::UnixEpoch());
+  FakePrefs fake_prefs;
+  SetUpStagingTest(kValidStagingSchedule, &fake_prefs);
+
+  attempter_.Update("", "", "", "", false, false, /* interactive = */ true);
+  CheckStagingOff();
+
+  ScheduleQuitMainLoop();
+}
+
+TEST_F(UpdateAttempterTest, StagingOffIfOobe) {
+  loop_.PostTask(FROM_HERE,
+                 base::Bind(&UpdateAttempterTest::StagingOffIfOobeStart,
+                            base::Unretained(this)));
+  loop_.Run();
+}
+
+void UpdateAttempterTest::StagingOffIfOobeStart() {
+  // Tests that staging is turned off if OOBE hasn't been completed.
+  fake_system_state_.fake_hardware()->SetIsOOBEEnabled(true);
+  fake_system_state_.fake_hardware()->UnsetIsOOBEComplete();
+  FakePrefs fake_prefs;
+  SetUpStagingTest(kValidStagingSchedule, &fake_prefs);
+
+  attempter_.Update("", "", "", "", false, false, /* interactive = */ true);
+  CheckStagingOff();
+
+  ScheduleQuitMainLoop();
+}
+
 // Checks that we only report daily metrics at most every 24 hours.
 TEST_F(UpdateAttempterTest, ReportDailyMetrics) {
   FakeClock fake_clock;
@@ -1044,37 +1173,56 @@
 }
 
 TEST_F(UpdateAttempterTest, TargetVersionPrefixSetAndReset) {
-  attempter_.CalculateUpdateParams("", "", "", "1234", false, false);
+  attempter_.CalculateUpdateParams("", "", "", "1234", false, false, false);
   EXPECT_EQ("1234",
             fake_system_state_.request_params()->target_version_prefix());
 
-  attempter_.CalculateUpdateParams("", "", "", "", false, false);
+  attempter_.CalculateUpdateParams("", "", "", "", false, false, false);
   EXPECT_TRUE(
       fake_system_state_.request_params()->target_version_prefix().empty());
 }
 
+TEST_F(UpdateAttempterTest, RollbackAllowedSetAndReset) {
+  attempter_.CalculateUpdateParams("",
+                                   "",
+                                   "",
+                                   "1234",
+                                   /*rollback_allowed=*/true,
+                                   false,
+                                   false);
+  EXPECT_TRUE(fake_system_state_.request_params()->rollback_allowed());
+
+  attempter_.CalculateUpdateParams("",
+                                   "",
+                                   "",
+                                   "1234",
+                                   /*rollback_allowed=*/false,
+                                   false,
+                                   false);
+  EXPECT_FALSE(fake_system_state_.request_params()->rollback_allowed());
+}
+
 TEST_F(UpdateAttempterTest, UpdateDeferredByPolicyTest) {
   // Construct an OmahaResponseHandlerAction that has processed an InstallPlan,
   // but the update is being deferred by the Policy.
-  OmahaResponseHandlerAction* response_action =
-      new OmahaResponseHandlerAction(&fake_system_state_);
-  response_action->install_plan_.version = "a.b.c.d";
-  response_action->install_plan_.system_version = "b.c.d.e";
-  response_action->install_plan_.payloads.push_back(
+  OmahaResponseHandlerAction response_action(&fake_system_state_);
+  response_action.install_plan_.version = "a.b.c.d";
+  response_action.install_plan_.system_version = "b.c.d.e";
+  response_action.install_plan_.payloads.push_back(
       {.size = 1234ULL, .type = InstallPayloadType::kFull});
-  attempter_.response_handler_action_.reset(response_action);
   // Inform the UpdateAttempter that the OmahaResponseHandlerAction has
   // completed, with the deferred-update error code.
   attempter_.ActionCompleted(
-      nullptr, response_action, ErrorCode::kOmahaUpdateDeferredPerPolicy);
+      nullptr, &response_action, ErrorCode::kOmahaUpdateDeferredPerPolicy);
   {
     UpdateEngineStatus status;
     attempter_.GetStatus(&status);
     EXPECT_EQ(UpdateStatus::UPDATE_AVAILABLE, status.status);
-    EXPECT_EQ(response_action->install_plan_.version, status.new_version);
-    EXPECT_EQ(response_action->install_plan_.system_version,
+    EXPECT_TRUE(attempter_.install_plan_);
+    EXPECT_EQ(attempter_.install_plan_->version, status.new_version);
+    EXPECT_EQ(attempter_.install_plan_->system_version,
               status.new_system_version);
-    EXPECT_EQ(response_action->install_plan_.payloads[0].size,
+    EXPECT_EQ(attempter_.install_plan_->payloads[0].size,
               status.new_size_bytes);
   }
   // An "error" event should have been created to tell Omaha that the update is
@@ -1093,10 +1241,10 @@
     UpdateEngineStatus status;
     attempter_.GetStatus(&status);
     EXPECT_EQ(UpdateStatus::REPORTING_ERROR_EVENT, status.status);
-    EXPECT_EQ(response_action->install_plan_.version, status.new_version);
-    EXPECT_EQ(response_action->install_plan_.system_version,
+    EXPECT_EQ(response_action.install_plan_.version, status.new_version);
+    EXPECT_EQ(response_action.install_plan_.system_version,
               status.new_system_version);
-    EXPECT_EQ(response_action->install_plan_.payloads[0].size,
+    EXPECT_EQ(response_action.install_plan_.payloads[0].size,
               status.new_size_bytes);
   }
 }
@@ -1118,6 +1266,20 @@
             attempter_.GetCurrentUpdateAttemptFlags());
 }
 
+TEST_F(UpdateAttempterTest, RollbackNotAllowed) {
+  UpdateCheckParams params = {.updates_enabled = true,
+                              .rollback_allowed = false};
+  attempter_.OnUpdateScheduled(EvalStatus::kSucceeded, params);
+  EXPECT_FALSE(fake_system_state_.request_params()->rollback_allowed());
+}
+
+TEST_F(UpdateAttempterTest, RollbackAllowed) {
+  UpdateCheckParams params = {.updates_enabled = true,
+                              .rollback_allowed = true};
+  attempter_.OnUpdateScheduled(EvalStatus::kSucceeded, params);
+  EXPECT_TRUE(fake_system_state_.request_params()->rollback_allowed());
+}
+
 TEST_F(UpdateAttempterTest, InteractiveUpdateUsesPassedRestrictions) {
   attempter_.SetUpdateAttemptFlags(UpdateAttemptFlags::kFlagRestrictDownload);
 
@@ -1139,4 +1301,124 @@
             attempter_.GetCurrentUpdateAttemptFlags());
 }
 
+void UpdateAttempterTest::ResetRollbackHappenedStart(bool is_consumer,
+                                                     bool is_policy_loaded,
+                                                     bool expected_reset) {
+  EXPECT_CALL(*fake_system_state_.mock_payload_state(), GetRollbackHappened())
+      .WillRepeatedly(Return(true));
+  auto mock_policy_provider =
+      std::make_unique<NiceMock<policy::MockPolicyProvider>>();
+  EXPECT_CALL(*mock_policy_provider, IsConsumerDevice())
+      .WillRepeatedly(Return(is_consumer));
+  EXPECT_CALL(*mock_policy_provider, device_policy_is_loaded())
+      .WillRepeatedly(Return(is_policy_loaded));
+  const policy::MockDevicePolicy device_policy;
+  EXPECT_CALL(*mock_policy_provider, GetDevicePolicy())
+      .WillRepeatedly(ReturnRef(device_policy));
+  EXPECT_CALL(*fake_system_state_.mock_payload_state(),
+              SetRollbackHappened(false))
+      .Times(expected_reset ? 1 : 0);
+  attempter_.policy_provider_ = std::move(mock_policy_provider);
+  attempter_.Update("", "", "", "", false, false, false);
+  ScheduleQuitMainLoop();
+}
+
+TEST_F(UpdateAttempterTest, ResetRollbackHappenedOobe) {
+  loop_.PostTask(FROM_HERE,
+                 base::Bind(&UpdateAttempterTest::ResetRollbackHappenedStart,
+                            base::Unretained(this),
+                            /*is_consumer=*/false,
+                            /*is_policy_loaded=*/false,
+                            /*expected_reset=*/false));
+  loop_.Run();
+}
+
+TEST_F(UpdateAttempterTest, ResetRollbackHappenedConsumer) {
+  loop_.PostTask(FROM_HERE,
+                 base::Bind(&UpdateAttempterTest::ResetRollbackHappenedStart,
+                            base::Unretained(this),
+                            /*is_consumer=*/true,
+                            /*is_policy_loaded=*/false,
+                            /*expected_reset=*/true));
+  loop_.Run();
+}
+
+TEST_F(UpdateAttempterTest, ResetRollbackHappenedEnterprise) {
+  loop_.PostTask(FROM_HERE,
+                 base::Bind(&UpdateAttempterTest::ResetRollbackHappenedStart,
+                            base::Unretained(this),
+                            /*is_consumer=*/false,
+                            /*is_policy_loaded=*/true,
+                            /*expected_reset=*/true));
+  loop_.Run();
+}
+
+TEST_F(UpdateAttempterTest, SetRollbackHappenedRollback) {
+  attempter_.install_plan_.reset(new InstallPlan);
+  attempter_.install_plan_->is_rollback = true;
+
+  EXPECT_CALL(*fake_system_state_.mock_payload_state(),
+              SetRollbackHappened(true))
+      .Times(1);
+  attempter_.ProcessingDone(nullptr, ErrorCode::kSuccess);
+}
+
+TEST_F(UpdateAttempterTest, SetRollbackHappenedNotRollback) {
+  attempter_.install_plan_.reset(new InstallPlan);
+  attempter_.install_plan_->is_rollback = false;
+
+  EXPECT_CALL(*fake_system_state_.mock_payload_state(),
+              SetRollbackHappened(true))
+      .Times(0);
+  attempter_.ProcessingDone(nullptr, ErrorCode::kSuccess);
+}
+
+TEST_F(UpdateAttempterTest, RollbackMetricsRollbackSuccess) {
+  attempter_.install_plan_.reset(new InstallPlan);
+  attempter_.install_plan_->is_rollback = true;
+  attempter_.install_plan_->version = kRollbackVersion;
+
+  EXPECT_CALL(*fake_system_state_.mock_metrics_reporter(),
+              ReportEnterpriseRollbackMetrics(true, kRollbackVersion))
+      .Times(1);
+  attempter_.ProcessingDone(nullptr, ErrorCode::kSuccess);
+}
+
+TEST_F(UpdateAttempterTest, RollbackMetricsNotRollbackSuccess) {
+  attempter_.install_plan_.reset(new InstallPlan);
+  attempter_.install_plan_->is_rollback = false;
+  attempter_.install_plan_->version = kRollbackVersion;
+
+  EXPECT_CALL(*fake_system_state_.mock_metrics_reporter(),
+              ReportEnterpriseRollbackMetrics(_, _))
+      .Times(0);
+  attempter_.ProcessingDone(nullptr, ErrorCode::kSuccess);
+}
+
+TEST_F(UpdateAttempterTest, RollbackMetricsRollbackFailure) {
+  attempter_.install_plan_.reset(new InstallPlan);
+  attempter_.install_plan_->is_rollback = true;
+  attempter_.install_plan_->version = kRollbackVersion;
+
+  EXPECT_CALL(*fake_system_state_.mock_metrics_reporter(),
+              ReportEnterpriseRollbackMetrics(false, kRollbackVersion))
+      .Times(1);
+  MockAction action;
+  attempter_.CreatePendingErrorEvent(&action, ErrorCode::kRollbackNotPossible);
+  attempter_.ProcessingDone(nullptr, ErrorCode::kRollbackNotPossible);
+}
+
+TEST_F(UpdateAttempterTest, RollbackMetricsNotRollbackFailure) {
+  attempter_.install_plan_.reset(new InstallPlan);
+  attempter_.install_plan_->is_rollback = false;
+  attempter_.install_plan_->version = kRollbackVersion;
+
+  EXPECT_CALL(*fake_system_state_.mock_metrics_reporter(),
+              ReportEnterpriseRollbackMetrics(_, _))
+      .Times(0);
+  MockAction action;
+  attempter_.CreatePendingErrorEvent(&action, ErrorCode::kRollbackNotPossible);
+  attempter_.ProcessingDone(nullptr, ErrorCode::kRollbackNotPossible);
+}
+
 }  // namespace chromeos_update_engine
diff --git a/update_boot_flags_action.cc b/update_boot_flags_action.cc
new file mode 100644
index 0000000..97ef7f2
--- /dev/null
+++ b/update_boot_flags_action.cc
@@ -0,0 +1,68 @@
+//
+// Copyright (C) 2018 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_boot_flags_action.h"
+
+#include <base/bind.h>
+#include <base/logging.h>
+
+#include "update_engine/common/boot_control.h"
+
+namespace chromeos_update_engine {
+
+bool UpdateBootFlagsAction::updated_boot_flags_ = false;
+bool UpdateBootFlagsAction::is_running_ = false;
+
+void UpdateBootFlagsAction::PerformAction() {
+  if (is_running_) {
+    LOG(INFO) << "Update boot flags running, nothing to do.";
+    processor_->ActionComplete(this, ErrorCode::kSuccess);
+    return;
+  }
+  if (updated_boot_flags_) {
+    LOG(INFO) << "Already updated boot flags. Skipping.";
+    processor_->ActionComplete(this, ErrorCode::kSuccess);
+    return;
+  }
+
+  // This is purely best effort. Failures should be logged by Subprocess. Run
+  // the script asynchronously to avoid blocking the event loop regardless of
+  // the script runtime.
+  is_running_ = true;
+  LOG(INFO) << "Marking booted slot as good.";
+  if (!boot_control_->MarkBootSuccessfulAsync(
+          base::Bind(&UpdateBootFlagsAction::CompleteUpdateBootFlags,
+                     base::Unretained(this)))) {
+    CompleteUpdateBootFlags(false);
+  }
+}
+
+void UpdateBootFlagsAction::CompleteUpdateBootFlags(bool successful) {
+  is_running_ = false;
+  if (!successful) {
+    // We ignore the failure for now because if the updating boot flags is flaky
+    // or has a bug in a specific release, then blocking the update can cause
+    // devices to stay behind even though we could have updated the system and
+    // fixed the issue regardless of this failure.
+    //
+    // TODO(ahassani): Add new error code metric for kUpdateBootFlagsFailed.
+    LOG(ERROR) << "Updating boot flags failed, but ignoring its failure.";
+  }
+  updated_boot_flags_ = true;
+  processor_->ActionComplete(this, ErrorCode::kSuccess);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_boot_flags_action.h b/update_boot_flags_action.h
new file mode 100644
index 0000000..0d1125e
--- /dev/null
+++ b/update_boot_flags_action.h
@@ -0,0 +1,57 @@
+//
+// Copyright (C) 2018 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 <string>
+
+#include "update_engine/common/action.h"
+#include "update_engine/common/boot_control_interface.h"
+
+namespace chromeos_update_engine {
+
+class UpdateBootFlagsAction : public AbstractAction {
+ public:
+  explicit UpdateBootFlagsAction(BootControlInterface* boot_control)
+      : boot_control_(boot_control) {}
+
+  void PerformAction() override;
+
+  static std::string StaticType() { return "UpdateBootFlagsAction"; }
+  std::string Type() const override { return StaticType(); }
+
+  void CompleteUpdateBootFlags(bool successful);
+
+ private:
+  FRIEND_TEST(UpdateBootFlagsActionTest, SimpleTest);
+  FRIEND_TEST(UpdateBootFlagsActionTest, DoubleActionTest);
+
+  // Originally, both of these flags are false. Once UpdateBootFlags is called,
+  // |is_running_| is set to true. As soon as UpdateBootFlags completes its
+  // asynchronous run, |is_running_| is reset to false and |updated_boot_flags_|
+  // is set to true. From that point on there will be no more changes to these
+  // flags.
+  //
+  // True if have updated the boot flags.
+  static bool updated_boot_flags_;
+  // True if we are still updating the boot flags.
+  static bool is_running_;
+
+  // Used for setting the boot flag.
+  BootControlInterface* boot_control_;
+
+  DISALLOW_COPY_AND_ASSIGN(UpdateBootFlagsAction);
+};
+
+}  // namespace chromeos_update_engine
diff --git a/update_boot_flags_action_unittest.cc b/update_boot_flags_action_unittest.cc
new file mode 100644
index 0000000..1b2bfa5
--- /dev/null
+++ b/update_boot_flags_action_unittest.cc
@@ -0,0 +1,69 @@
+//
+// Copyright (C) 2018 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_boot_flags_action.h"
+
+#include <memory>
+#include <utility>
+
+#include <base/bind.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/fake_system_state.h"
+
+namespace chromeos_update_engine {
+
+class UpdateBootFlagsActionTest : public ::testing::Test {
+ public:
+  FakeSystemState fake_system_state_;
+};
+
+TEST_F(UpdateBootFlagsActionTest, SimpleTest) {
+  auto boot_control = fake_system_state_.fake_boot_control();
+  auto action = std::make_unique<UpdateBootFlagsAction>(boot_control);
+  ActionProcessor processor;
+  processor.EnqueueAction(std::move(action));
+
+  EXPECT_FALSE(UpdateBootFlagsAction::updated_boot_flags_);
+  EXPECT_FALSE(UpdateBootFlagsAction::is_running_);
+  processor.StartProcessing();
+  EXPECT_TRUE(UpdateBootFlagsAction::updated_boot_flags_);
+  EXPECT_FALSE(UpdateBootFlagsAction::is_running_);
+}
+
+TEST_F(UpdateBootFlagsActionTest, DoubleActionTest) {
+  // Reset the static flags.
+  UpdateBootFlagsAction::updated_boot_flags_ = false;
+  UpdateBootFlagsAction::is_running_ = false;
+
+  auto boot_control = fake_system_state_.fake_boot_control();
+  auto action1 = std::make_unique<UpdateBootFlagsAction>(boot_control);
+  auto action2 = std::make_unique<UpdateBootFlagsAction>(boot_control);
+  ActionProcessor processor1, processor2;
+  processor1.EnqueueAction(std::move(action1));
+  processor2.EnqueueAction(std::move(action2));
+
+  EXPECT_FALSE(UpdateBootFlagsAction::updated_boot_flags_);
+  EXPECT_FALSE(UpdateBootFlagsAction::is_running_);
+  processor1.StartProcessing();
+  EXPECT_TRUE(UpdateBootFlagsAction::updated_boot_flags_);
+  EXPECT_FALSE(UpdateBootFlagsAction::is_running_);
+  processor2.StartProcessing();
+  EXPECT_TRUE(UpdateBootFlagsAction::updated_boot_flags_);
+  EXPECT_FALSE(UpdateBootFlagsAction::is_running_);
+}
+
+}  // namespace chromeos_update_engine
diff --git a/update_engine.gyp b/update_engine.gyp
index 85632a2..ee2471a 100644
--- a/update_engine.gyp
+++ b/update_engine.gyp
@@ -104,16 +104,16 @@
       'includes': ['../../../platform2/common-mk/generate-dbus-adaptors.gypi'],
     },
     {
-      'target_name': 'update_engine-dbus-libcros-client',
+      'target_name': 'update_engine-dbus-kiosk-app-client',
       'type': 'none',
       'actions': [{
-        'action_name': 'update_engine-dbus-libcros-client-action',
+        'action_name': 'update_engine-dbus-kiosk-app-client-action',
         'variables': {
-          'mock_output_file': 'include/libcros/dbus-proxy-mocks.h',
-          'proxy_output_file': 'include/libcros/dbus-proxies.h',
+          'mock_output_file': 'include/kiosk-app/dbus-proxy-mocks.h',
+          'proxy_output_file': 'include/kiosk-app/dbus-proxies.h',
         },
         'sources': [
-          'dbus_bindings/org.chromium.LibCrosService.dbus-xml',
+          'dbus_bindings/org.chromium.KioskAppService.dbus-xml',
         ],
         'includes': ['../../../platform2/common-mk/generate-dbus-proxies.gypi'],
       }],
@@ -273,6 +273,7 @@
         'real_system_state.cc',
         'shill_proxy.cc',
         'update_attempter.cc',
+        'update_boot_flags_action.cc',
         'update_manager/boxed_value.cc',
         'update_manager/chromeos_policy.cc',
         'update_manager/default_policy.cc',
@@ -292,8 +293,11 @@
         'update_manager/real_system_provider.cc',
         'update_manager/real_time_provider.cc',
         'update_manager/real_updater_provider.cc',
+        'update_manager/staging_utils.cc',
         'update_manager/state_factory.cc',
         'update_manager/update_manager.cc',
+        'update_manager/update_time_restrictions_policy_impl.cc',
+        'update_manager/weekly_time.cc',
         'update_status_utils.cc',
       ],
       'conditions': [
@@ -304,7 +308,7 @@
         }],
         ['USE_chrome_kiosk_app == 1', {
           'dependencies': [
-            'update_engine-dbus-libcros-client',
+            'update_engine-dbus-kiosk-app-client',
           ],
         }],
       ],
@@ -568,6 +572,7 @@
             'proxy_resolver_unittest.cc',
             'testrunner.cc',
             'update_attempter_unittest.cc',
+            'update_boot_flags_action_unittest.cc',
             'update_manager/boxed_value_unittest.cc',
             'update_manager/chromeos_policy_unittest.cc',
             'update_manager/evaluation_context_unittest.cc',
@@ -579,9 +584,12 @@
             'update_manager/real_system_provider_unittest.cc',
             'update_manager/real_time_provider_unittest.cc',
             'update_manager/real_updater_provider_unittest.cc',
+            'update_manager/staging_utils_unittest.cc',
             'update_manager/umtest_utils.cc',
             'update_manager/update_manager_unittest.cc',
+            'update_manager/update_time_restrictions_policy_impl_unittest.cc',
             'update_manager/variable_unittest.cc',
+            'update_manager/weekly_time_unittest.cc',
           ],
         },
       ],
diff --git a/update_engine_client.cc b/update_engine_client.cc
index 55206b2..b7096c5 100644
--- a/update_engine_client.cc
+++ b/update_engine_client.cc
@@ -458,8 +458,8 @@
       LOG(INFO) << "Target Channel (pending update): " << target_channel;
   }
 
-  bool do_update_request = FLAGS_check_for_update | FLAGS_update |
-                           !FLAGS_app_version.empty() |
+  bool do_update_request = FLAGS_check_for_update || FLAGS_update ||
+                           !FLAGS_app_version.empty() ||
                            !FLAGS_omaha_url.empty();
   if (FLAGS_update) FLAGS_follow = true;
 
diff --git a/update_manager/android_things_policy.cc b/update_manager/android_things_policy.cc
index 20d10d8..4afcf12 100644
--- a/update_manager/android_things_policy.cc
+++ b/update_manager/android_things_policy.cc
@@ -53,6 +53,8 @@
   result->updates_enabled = true;
   result->target_channel.clear();
   result->target_version_prefix.clear();
+  result->rollback_allowed = false;
+  result->rollback_allowed_milestones = -1;
   result->interactive = false;
 
   // Build a list of policies to consult.  Note that each policy may modify the
diff --git a/update_manager/boxed_value.cc b/update_manager/boxed_value.cc
index a437c02..971e9b7 100644
--- a/update_manager/boxed_value.cc
+++ b/update_manager/boxed_value.cc
@@ -26,8 +26,10 @@
 
 #include "update_engine/common/utils.h"
 #include "update_engine/connection_utils.h"
+#include "update_engine/update_manager/rollback_prefs.h"
 #include "update_engine/update_manager/shill_provider.h"
 #include "update_engine/update_manager/updater_provider.h"
+#include "update_engine/update_manager/weekly_time.h"
 
 using chromeos_update_engine::ConnectionTethering;
 using chromeos_update_engine::ConnectionType;
@@ -133,6 +135,25 @@
   return "Unknown";
 }
 
+template <>
+string BoxedValue::ValuePrinter<RollbackToTargetVersion>(const void* value) {
+  const RollbackToTargetVersion* val =
+      reinterpret_cast<const RollbackToTargetVersion*>(value);
+  switch (*val) {
+    case RollbackToTargetVersion::kUnspecified:
+      return "Unspecified";
+    case RollbackToTargetVersion::kDisabled:
+      return "Disabled";
+    case RollbackToTargetVersion::kRollbackWithFullPowerwash:
+      return "Rollback with full powerwash";
+    case RollbackToTargetVersion::kMaxValue:
+      NOTREACHED();
+      return "Max value";
+  }
+  NOTREACHED();
+  return "Unknown";
+}
+
 template<>
 string BoxedValue::ValuePrinter<Stage>(const void* value) {
   const Stage* val = reinterpret_cast<const Stage*>(value);
@@ -191,4 +212,23 @@
   return retval;
 }
 
+template <>
+string BoxedValue::ValuePrinter<WeeklyTimeInterval>(const void* value) {
+  const WeeklyTimeInterval* val =
+      reinterpret_cast<const WeeklyTimeInterval*>(value);
+  return val->ToString();
+}
+
+template <>
+string BoxedValue::ValuePrinter<WeeklyTimeIntervalVector>(const void* value) {
+  const WeeklyTimeIntervalVector* val =
+      reinterpret_cast<const WeeklyTimeIntervalVector*>(value);
+
+  string retval = "Disallowed intervals:\n";
+  for (const auto& interval : *val) {
+    retval += interval.ToString() + "\n";
+  }
+  return retval;
+}
+
 }  // namespace chromeos_update_manager
diff --git a/update_manager/boxed_value_unittest.cc b/update_manager/boxed_value_unittest.cc
index 4aeaec8..3fa0f1a 100644
--- a/update_manager/boxed_value_unittest.cc
+++ b/update_manager/boxed_value_unittest.cc
@@ -26,9 +26,11 @@
 #include <base/strings/stringprintf.h>
 #include <base/time/time.h>
 
+#include "update_engine/update_manager/rollback_prefs.h"
 #include "update_engine/update_manager/shill_provider.h"
 #include "update_engine/update_manager/umtest_utils.h"
 #include "update_engine/update_manager/updater_provider.h"
+#include "update_engine/update_manager/weekly_time.h"
 
 using base::Time;
 using base::TimeDelta;
@@ -192,6 +194,21 @@
             .ToString());
 }
 
+TEST(UmBoxedValueTest, RollbackToTargetVersionToString) {
+  EXPECT_EQ("Unspecified",
+            BoxedValue(new RollbackToTargetVersion(
+                           RollbackToTargetVersion::kUnspecified))
+                .ToString());
+  EXPECT_EQ("Disabled",
+            BoxedValue(
+                new RollbackToTargetVersion(RollbackToTargetVersion::kDisabled))
+                .ToString());
+  EXPECT_EQ("Rollback with full powerwash",
+            BoxedValue(new RollbackToTargetVersion(
+                           RollbackToTargetVersion::kRollbackWithFullPowerwash))
+                .ToString());
+}
+
 TEST(UmBoxedValueTest, SetConnectionTypeToString) {
   set<ConnectionType>* set1 = new set<ConnectionType>;
   set1->insert(ConnectionType::kWimax);
@@ -242,4 +259,34 @@
                 .ToString());
 }
 
+TEST(UmBoxedValueTest, WeeklyTimeIntervalToString) {
+  EXPECT_EQ("Start: day_of_week=2 time=100\nEnd: day_of_week=4 time=200",
+            BoxedValue(new WeeklyTimeInterval(
+                           WeeklyTime(2, TimeDelta::FromMinutes(100)),
+                           WeeklyTime(4, TimeDelta::FromMinutes(200))))
+                .ToString());
+  EXPECT_EQ("Start: day_of_week=1 time=10\nEnd: day_of_week=1 time=20",
+            BoxedValue(new WeeklyTimeInterval(
+                           WeeklyTime(1, TimeDelta::FromMinutes(10)),
+                           WeeklyTime(1, TimeDelta::FromMinutes(20))))
+                .ToString());
+}
+
+TEST(UmBoxedValueTest, WeeklyTimeIntervalVectorToString) {
+  WeeklyTimeIntervalVector intervals;
+  intervals.emplace_back(WeeklyTime(5, TimeDelta::FromMinutes(10)),
+                         WeeklyTime(1, TimeDelta::FromMinutes(30)));
+  EXPECT_EQ(
+      "Disallowed intervals:\nStart: day_of_week=5 time=10\nEnd: "
+      "day_of_week=1 time=30\n",
+      BoxedValue(new WeeklyTimeIntervalVector(intervals)).ToString());
+  intervals.emplace_back(WeeklyTime(1, TimeDelta::FromMinutes(5)),
+                         WeeklyTime(6, TimeDelta::FromMinutes(1000)));
+  EXPECT_EQ(
+      "Disallowed intervals:\nStart: day_of_week=5 time=10\nEnd: "
+      "day_of_week=1 time=30\nStart: day_of_week=1 time=5\nEnd: day_of_week=6 "
+      "time=1000\n",
+      BoxedValue(new WeeklyTimeIntervalVector(intervals)).ToString());
+}
+
 }  // namespace chromeos_update_manager
diff --git a/update_manager/chromeos_policy.cc b/update_manager/chromeos_policy.cc
index 0947603..71fec40 100644
--- a/update_manager/chromeos_policy.cc
+++ b/update_manager/chromeos_policy.cc
@@ -36,6 +36,7 @@
 #include "update_engine/update_manager/out_of_box_experience_policy_impl.h"
 #include "update_engine/update_manager/policy_utils.h"
 #include "update_engine/update_manager/shill_provider.h"
+#include "update_engine/update_manager/update_time_restrictions_policy_impl.h"
 
 using base::Time;
 using base::TimeDelta;
@@ -142,8 +143,11 @@
     case ErrorCode::kOmahaRequestXMLHasEntityDecl:
     case ErrorCode::kFilesystemVerifierError:
     case ErrorCode::kUserCanceled:
+    case ErrorCode::kOmahaUpdateIgnoredOverCellular:
     case ErrorCode::kUpdatedButNotActive:
     case ErrorCode::kNoUpdate:
+    case ErrorCode::kRollbackNotPossible:
+    case ErrorCode::kFirstActiveOmahaPingSentPersistenceError:
       LOG(INFO) << "Not changing URL index or failure count due to error "
                 << chromeos_update_engine::utils::ErrorCodeToString(err_code)
                 << " (" << static_cast<int>(err_code) << ")";
@@ -200,6 +204,8 @@
   result->updates_enabled = true;
   result->target_channel.clear();
   result->target_version_prefix.clear();
+  result->rollback_allowed = false;
+  result->rollback_allowed_milestones = -1;
   result->interactive = false;
 
   EnoughSlotsAbUpdatesPolicyImpl enough_slots_ab_updates_policy;
@@ -256,8 +262,33 @@
                                               std::string* error,
                                               ErrorCode* result,
                                               InstallPlan* install_plan) const {
-  *result = ErrorCode::kSuccess;
-  return EvalStatus::kSucceeded;
+  UpdateTimeRestrictionsPolicyImpl update_time_restrictions_policy;
+  InteractiveUpdatePolicyImpl interactive_update_policy;
+
+  vector<Policy const*> policies_to_consult = {
+      // Check to see if an interactive update has been requested.
+      &interactive_update_policy,
+
+      // Do not apply or download an update if we are inside one of the
+      // restricted times.
+      &update_time_restrictions_policy,
+  };
+
+  EvalStatus status = ConsultPolicies(policies_to_consult,
+                                      &Policy::UpdateCanBeApplied,
+                                      ec,
+                                      state,
+                                      error,
+                                      result,
+                                      install_plan);
+  if (EvalStatus::kContinue != status) {
+    return status;
+  } else {
+    // The update can proceed.
+    LOG(INFO) << "Allowing update to be applied.";
+    *result = ErrorCode::kSuccess;
+    return EvalStatus::kSucceeded;
+  }
 }
 
 EvalStatus ChromeOSPolicy::UpdateCanStart(
diff --git a/update_manager/chromeos_policy.h b/update_manager/chromeos_policy.h
index 67c0d15..d4ce4a6 100644
--- a/update_manager/chromeos_policy.h
+++ b/update_manager/chromeos_policy.h
@@ -114,6 +114,8 @@
               UpdateCanStartAllowedP2PDownloadingBlockedDueToNumAttempts);
   FRIEND_TEST(UmChromeOSPolicyTest,
               UpdateCanStartAllowedP2PDownloadingBlockedDueToAttemptsPeriod);
+  FRIEND_TEST(UmChromeOSPolicyTest,
+              UpdateCheckAllowedNextUpdateCheckOutsideDisallowedInterval);
 
   // Auxiliary constant (zero by default).
   const base::TimeDelta kZeroInterval;
diff --git a/update_manager/chromeos_policy_unittest.cc b/update_manager/chromeos_policy_unittest.cc
index 095f516..96f3d79 100644
--- a/update_manager/chromeos_policy_unittest.cc
+++ b/update_manager/chromeos_policy_unittest.cc
@@ -21,12 +21,14 @@
 
 #include "update_engine/update_manager/next_update_check_policy_impl.h"
 #include "update_engine/update_manager/policy_test_utils.h"
+#include "update_engine/update_manager/weekly_time.h"
 
 using base::Time;
 using base::TimeDelta;
 using chromeos_update_engine::ConnectionTethering;
 using chromeos_update_engine::ConnectionType;
 using chromeos_update_engine::ErrorCode;
+using chromeos_update_engine::InstallPlan;
 using std::set;
 using std::string;
 
@@ -83,6 +85,9 @@
         new bool(false));
     fake_state_.device_policy_provider()->var_release_channel_delegated()->
         reset(new bool(true));
+    fake_state_.device_policy_provider()
+        ->var_disallowed_time_intervals()
+        ->reset(new WeeklyTimeIntervalVector());
   }
 
   // Configures the policy to return a desired value from UpdateCheckAllowed by
@@ -105,6 +110,67 @@
       curr_time -= TimeDelta::FromSeconds(1);
     fake_clock_.SetWallclockTime(curr_time);
   }
+
+  // Sets the policies required for a kiosk app to control Chrome OS version:
+  // - AllowKioskAppControlChromeVersion = True
+  // - UpdateDisabled = True
+  // In the kiosk app manifest:
+  // - RequiredPlatformVersion = 1234.
+  void SetKioskAppControlsChromeOsVersion() {
+    fake_state_.device_policy_provider()
+        ->var_allow_kiosk_app_control_chrome_version()
+        ->reset(new bool(true));
+    fake_state_.device_policy_provider()->var_update_disabled()->reset(
+        new bool(true));
+    fake_state_.system_provider()->var_kiosk_required_platform_version()->reset(
+        new string("1234."));
+  }
+
+  // Sets up a test with the value of RollbackToTargetVersion policy (and
+  // whether it's set), and returns the value of
+  // UpdateCheckParams.rollback_allowed.
+  bool TestRollbackAllowed(bool set_policy,
+                           RollbackToTargetVersion rollback_to_target_version) {
+    // Update check is allowed, response includes attributes for use in the
+    // request.
+    SetUpdateCheckAllowed(true);
+
+    if (set_policy) {
+      // Override RollbackToTargetVersion device policy attribute.
+      fake_state_.device_policy_provider()
+          ->var_rollback_to_target_version()
+          ->reset(new RollbackToTargetVersion(rollback_to_target_version));
+    }
+
+    UpdateCheckParams result;
+    ExpectPolicyStatus(
+        EvalStatus::kSucceeded, &Policy::UpdateCheckAllowed, &result);
+    return result.rollback_allowed;
+  }
+
+  // Sets up a test with the given intervals and the current fake wallclock
+  // time.
+  void TestDisallowedTimeIntervals(const WeeklyTimeIntervalVector& intervals,
+                                   const ErrorCode& expected_error_code,
+                                   bool kiosk) {
+    SetUpDefaultTimeProvider();
+    if (kiosk)
+      fake_state_.device_policy_provider()
+          ->var_auto_launched_kiosk_app_id()
+          ->reset(new string("myapp"));
+    fake_state_.device_policy_provider()
+        ->var_disallowed_time_intervals()
+        ->reset(new WeeklyTimeIntervalVector(intervals));
+
+    // Check that |expected_status| matches the value of UpdateCheckAllowed
+    ErrorCode result;
+    InstallPlan install_plan;
+    ExpectPolicyStatus(EvalStatus::kSucceeded,
+                       &Policy::UpdateCanBeApplied,
+                       &result,
+                       &install_plan);
+    EXPECT_EQ(result, expected_error_code);
+  }
 };
 
 TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedWaitsForTheTimeout) {
@@ -189,8 +255,11 @@
   // Override specific device policy attributes.
   fake_state_.device_policy_provider()->var_target_version_prefix()->
       reset(new string("1.2"));
-  fake_state_.device_policy_provider()->var_release_channel_delegated()->
-      reset(new bool(false));
+  fake_state_.device_policy_provider()
+      ->var_rollback_allowed_milestones()
+      ->reset(new int(5));
+  fake_state_.device_policy_provider()->var_release_channel_delegated()->reset(
+      new bool(false));
   fake_state_.device_policy_provider()->var_release_channel()->
       reset(new string("foo-channel"));
 
@@ -199,10 +268,57 @@
                      &Policy::UpdateCheckAllowed, &result);
   EXPECT_TRUE(result.updates_enabled);
   EXPECT_EQ("1.2", result.target_version_prefix);
+  EXPECT_EQ(5, result.rollback_allowed_milestones);
   EXPECT_EQ("foo-channel", result.target_channel);
   EXPECT_FALSE(result.interactive);
 }
 
+TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedRollbackAllowed) {
+  EXPECT_TRUE(TestRollbackAllowed(
+      true, RollbackToTargetVersion::kRollbackWithFullPowerwash));
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedRollbackDisabled) {
+  EXPECT_FALSE(TestRollbackAllowed(true, RollbackToTargetVersion::kDisabled));
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedRollbackUnspecified) {
+  EXPECT_FALSE(
+      TestRollbackAllowed(true, RollbackToTargetVersion::kUnspecified));
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedRollbackNotSet) {
+  EXPECT_FALSE(
+      TestRollbackAllowed(false, RollbackToTargetVersion::kUnspecified));
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedKioskRollbackAllowed) {
+  SetKioskAppControlsChromeOsVersion();
+
+  EXPECT_TRUE(TestRollbackAllowed(
+      true, RollbackToTargetVersion::kRollbackWithFullPowerwash));
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedKioskRollbackDisabled) {
+  SetKioskAppControlsChromeOsVersion();
+
+  EXPECT_FALSE(TestRollbackAllowed(true, RollbackToTargetVersion::kDisabled));
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedKioskRollbackUnspecified) {
+  SetKioskAppControlsChromeOsVersion();
+
+  EXPECT_FALSE(
+      TestRollbackAllowed(true, RollbackToTargetVersion::kUnspecified));
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedKioskRollbackNotSet) {
+  SetKioskAppControlsChromeOsVersion();
+
+  EXPECT_FALSE(
+      TestRollbackAllowed(false, RollbackToTargetVersion::kUnspecified));
+}
+
 TEST_F(UmChromeOSPolicyTest,
        UpdateCheckAllowedUpdatesDisabledForUnofficialBuilds) {
   // UpdateCheckAllowed should return kAskMeAgainLater if this is an unofficial
@@ -278,21 +394,13 @@
   // Update check is allowed.
   SetUpdateCheckAllowed(true);
 
-  // A typical setup for kiosk pin policy: AU disabled, allow kiosk to pin
-  // and there is a kiosk required platform version.
-  fake_state_.device_policy_provider()->var_update_disabled()->reset(
-      new bool(true));
-  fake_state_.device_policy_provider()
-      ->var_allow_kiosk_app_control_chrome_version()
-      ->reset(new bool(true));
-  fake_state_.system_provider()->var_kiosk_required_platform_version()->reset(
-      new string("1234.0.0"));
+  SetKioskAppControlsChromeOsVersion();
 
   UpdateCheckParams result;
   ExpectPolicyStatus(EvalStatus::kSucceeded,
                      &Policy::UpdateCheckAllowed, &result);
   EXPECT_TRUE(result.updates_enabled);
-  EXPECT_EQ("1234.0.0", result.target_version_prefix);
+  EXPECT_EQ("1234.", result.target_version_prefix);
   EXPECT_FALSE(result.interactive);
 }
 
@@ -1496,4 +1604,48 @@
                      &result, false);
 }
 
+TEST_F(UmChromeOSPolicyTest,
+       UpdateCanBeAppliedForcedUpdatesDisablesTimeRestrictions) {
+  Time curr_time = fake_clock_.GetWallclockTime();
+  fake_state_.updater_provider()->var_forced_update_requested()->reset(
+      new UpdateRequestStatus(UpdateRequestStatus::kInteractive));
+  // Should return kAskMeAgainLater when updated are not forced.
+  TestDisallowedTimeIntervals(
+      {WeeklyTimeInterval(
+          WeeklyTime::FromTime(curr_time),
+          WeeklyTime::FromTime(curr_time + TimeDelta::FromMinutes(1)))},
+      ErrorCode::kSuccess,
+      /* kiosk = */ true);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanBeAppliedFailsInDisallowedTime) {
+  Time curr_time = fake_clock_.GetWallclockTime();
+  TestDisallowedTimeIntervals(
+      {WeeklyTimeInterval(
+          WeeklyTime::FromTime(curr_time),
+          WeeklyTime::FromTime(curr_time + TimeDelta::FromMinutes(1)))},
+      ErrorCode::kOmahaUpdateDeferredPerPolicy,
+      /* kiosk = */ true);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanBeAppliedOutsideDisallowedTime) {
+  Time curr_time = fake_clock_.GetWallclockTime();
+  TestDisallowedTimeIntervals(
+      {WeeklyTimeInterval(
+          WeeklyTime::FromTime(curr_time - TimeDelta::FromHours(3)),
+          WeeklyTime::FromTime(curr_time))},
+      ErrorCode::kSuccess,
+      /* kiosk = */ true);
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCanBeAppliedPassesOnNonKiosk) {
+  Time curr_time = fake_clock_.GetWallclockTime();
+  TestDisallowedTimeIntervals(
+      {WeeklyTimeInterval(
+          WeeklyTime::FromTime(curr_time),
+          WeeklyTime::FromTime(curr_time + TimeDelta::FromMinutes(1)))},
+      ErrorCode::kSuccess,
+      /* kiosk = */ false);
+}
+
 }  // namespace chromeos_update_manager
diff --git a/update_manager/default_policy.cc b/update_manager/default_policy.cc
index bc4f98e..5509abc 100644
--- a/update_manager/default_policy.cc
+++ b/update_manager/default_policy.cc
@@ -40,6 +40,8 @@
   result->updates_enabled = true;
   result->target_channel.clear();
   result->target_version_prefix.clear();
+  result->rollback_allowed = false;
+  result->rollback_allowed_milestones = -1;  // No version rolls should happen.
   result->interactive = false;
 
   // Ensure that the minimum interval is set. If there's no clock, this defaults
diff --git a/update_manager/device_policy_provider.h b/update_manager/device_policy_provider.h
index 3537d13..80dcfa2 100644
--- a/update_manager/device_policy_provider.h
+++ b/update_manager/device_policy_provider.h
@@ -24,8 +24,10 @@
 #include <policy/libpolicy.h>
 
 #include "update_engine/update_manager/provider.h"
+#include "update_engine/update_manager/rollback_prefs.h"
 #include "update_engine/update_manager/shill_provider.h"
 #include "update_engine/update_manager/variable.h"
+#include "update_engine/update_manager/weekly_time.h"
 
 namespace chromeos_update_manager {
 
@@ -46,6 +48,15 @@
 
   virtual Variable<std::string>* var_target_version_prefix() = 0;
 
+  // Variable returning what should happen if the target_version_prefix is
+  // earlier than the current Chrome OS version.
+  virtual Variable<RollbackToTargetVersion>*
+      var_rollback_to_target_version() = 0;
+
+  // Variable returning the number of Chrome milestones rollback should be
+  // possible. Rollback protection will be postponed by this many versions.
+  virtual Variable<int>* var_rollback_allowed_milestones() = 0;
+
   // Returns a non-negative scatter interval used for updates.
   virtual Variable<base::TimeDelta>* var_scatter_factor() = 0;
 
@@ -65,6 +76,15 @@
 
   virtual Variable<bool>* var_allow_kiosk_app_control_chrome_version() = 0;
 
+  // Variable that contains the app that is to be run when launched in kiosk
+  // mode. If the device is not in kiosk-mode this should be empty.
+  virtual Variable<std::string>* var_auto_launched_kiosk_app_id() = 0;
+
+  // Variable that contains the time intervals during the week for which update
+  // checks are disallowed.
+  virtual Variable<WeeklyTimeIntervalVector>*
+  var_disallowed_time_intervals() = 0;
+
  protected:
   DevicePolicyProvider() {}
 
diff --git a/update_manager/enterprise_device_policy_impl.cc b/update_manager/enterprise_device_policy_impl.cc
index 4e0def5..6f14b1f 100644
--- a/update_manager/enterprise_device_policy_impl.cc
+++ b/update_manager/enterprise_device_policy_impl.cc
@@ -55,6 +55,7 @@
       }
     }
 
+    // By default, result->rollback_allowed is false.
     if (kiosk_app_control_chrome_version) {
       // Get the required platform version from Chrome.
       const string* kiosk_required_platform_version_p =
@@ -68,6 +69,8 @@
       result->target_version_prefix = *kiosk_required_platform_version_p;
       LOG(INFO) << "Allow kiosk app to control Chrome version policy is set, "
                 << "target version is " << result->target_version_prefix;
+      // TODO(hunyadym): Add support for allowing rollback using the manifest
+      // (if policy doesn't specify otherwise).
     } else {
       // Determine whether a target version prefix is dictated by policy.
       const string* target_version_prefix_p =
@@ -76,6 +79,36 @@
         result->target_version_prefix = *target_version_prefix_p;
     }
 
+    // Policy always overwrites whether rollback is allowed by the kiosk app
+    // manifest.
+    const RollbackToTargetVersion* rollback_to_target_version_p =
+        ec->GetValue(dp_provider->var_rollback_to_target_version());
+    if (rollback_to_target_version_p) {
+      switch (*rollback_to_target_version_p) {
+        case RollbackToTargetVersion::kUnspecified:
+          // We leave the default or the one specified by the kiosk app.
+          break;
+        case RollbackToTargetVersion::kDisabled:
+          LOG(INFO) << "Policy disables rollbacks.";
+          result->rollback_allowed = false;
+          break;
+        case RollbackToTargetVersion::kRollbackWithFullPowerwash:
+          LOG(INFO) << "Policy allows rollbacks.";
+          result->rollback_allowed = true;
+          break;
+        case RollbackToTargetVersion::kMaxValue:
+          NOTREACHED();
+          // Don't add a default case to let the compiler warn about newly
+          // added enum values which should be added here.
+      }
+    }
+
+    // Determine allowed milestones for rollback
+    const int* rollback_allowed_milestones_p =
+        ec->GetValue(dp_provider->var_rollback_allowed_milestones());
+    if (rollback_allowed_milestones_p)
+      result->rollback_allowed_milestones = *rollback_allowed_milestones_p;
+
     // Determine whether a target channel is dictated by policy.
     const bool* release_channel_delegated_p =
         ec->GetValue(dp_provider->var_release_channel_delegated());
diff --git a/update_manager/fake_device_policy_provider.h b/update_manager/fake_device_policy_provider.h
index 9e4f5b7..d70e0c3 100644
--- a/update_manager/fake_device_policy_provider.h
+++ b/update_manager/fake_device_policy_provider.h
@@ -50,6 +50,15 @@
     return &var_target_version_prefix_;
   }
 
+  FakeVariable<RollbackToTargetVersion>* var_rollback_to_target_version()
+      override {
+    return &var_rollback_to_target_version_;
+  }
+
+  FakeVariable<int>* var_rollback_allowed_milestones() override {
+    return &var_rollback_allowed_milestones_;
+  }
+
   FakeVariable<base::TimeDelta>* var_scatter_factor() override {
     return &var_scatter_factor_;
   }
@@ -75,6 +84,15 @@
     return &var_allow_kiosk_app_control_chrome_version_;
   }
 
+  FakeVariable<std::string>* var_auto_launched_kiosk_app_id() override {
+    return &var_auto_launched_kiosk_app_id_;
+  }
+
+  FakeVariable<WeeklyTimeIntervalVector>* var_disallowed_time_intervals()
+      override {
+    return &var_disallowed_time_intervals_;
+  }
+
  private:
   FakeVariable<bool> var_device_policy_is_loaded_{
       "policy_is_loaded", kVariableModePoll};
@@ -86,6 +104,10 @@
       "update_disabled", kVariableModePoll};
   FakeVariable<std::string> var_target_version_prefix_{
       "target_version_prefix", kVariableModePoll};
+  FakeVariable<RollbackToTargetVersion> var_rollback_to_target_version_{
+      "rollback_to_target_version", kVariableModePoll};
+  FakeVariable<int> var_rollback_allowed_milestones_{
+      "rollback_allowed_milestones", kVariableModePoll};
   FakeVariable<base::TimeDelta> var_scatter_factor_{
       "scatter_factor", kVariableModePoll};
   FakeVariable<std::set<chromeos_update_engine::ConnectionType>>
@@ -97,6 +119,10 @@
   FakeVariable<bool> var_au_p2p_enabled_{"au_p2p_enabled", kVariableModePoll};
   FakeVariable<bool> var_allow_kiosk_app_control_chrome_version_{
       "allow_kiosk_app_control_chrome_version", kVariableModePoll};
+  FakeVariable<std::string> var_auto_launched_kiosk_app_id_{
+      "auto_launched_kiosk_app_id", kVariableModePoll};
+  FakeVariable<WeeklyTimeIntervalVector> var_disallowed_time_intervals_{
+      "disallowed_time_intervals", kVariableModePoll};
 
   DISALLOW_COPY_AND_ASSIGN(FakeDevicePolicyProvider);
 };
diff --git a/update_manager/fake_time_provider.h b/update_manager/fake_time_provider.h
index 2aea2e7..bd370d2 100644
--- a/update_manager/fake_time_provider.h
+++ b/update_manager/fake_time_provider.h
@@ -29,10 +29,12 @@
 
   FakeVariable<base::Time>* var_curr_date() override { return &var_curr_date_; }
   FakeVariable<int>* var_curr_hour() override { return &var_curr_hour_; }
+  FakeVariable<int>* var_curr_minute() override { return &var_curr_minute_; }
 
  private:
   FakeVariable<base::Time> var_curr_date_{"curr_date", kVariableModePoll};
   FakeVariable<int> var_curr_hour_{"curr_hour", kVariableModePoll};
+  FakeVariable<int> var_curr_minute_{"curr_minute", kVariableModePoll};
 
   DISALLOW_COPY_AND_ASSIGN(FakeTimeProvider);
 };
diff --git a/update_manager/interactive_update_policy_impl.cc b/update_manager/interactive_update_policy_impl.cc
index 03af435..872dc5d 100644
--- a/update_manager/interactive_update_policy_impl.cc
+++ b/update_manager/interactive_update_policy_impl.cc
@@ -16,6 +16,9 @@
 
 #include "update_engine/update_manager/interactive_update_policy_impl.h"
 
+using chromeos_update_engine::ErrorCode;
+using chromeos_update_engine::InstallPlan;
+
 namespace chromeos_update_manager {
 
 // Check to see if an interactive update was requested.
@@ -24,21 +27,51 @@
     State* state,
     std::string* error,
     UpdateCheckParams* result) const {
-  UpdaterProvider* const updater_provider = state->updater_provider();
+  bool interactive;
+  if (CheckInteractiveUpdateRequested(
+          ec, state->updater_provider(), &interactive)) {
+    result->interactive = interactive;
+    LOG(INFO) << "Forced update signaled ("
+              << (interactive ? "interactive" : "periodic")
+              << "), allowing update check.";
+    return EvalStatus::kSucceeded;
+  }
+  return EvalStatus::kContinue;
+}
 
+EvalStatus InteractiveUpdatePolicyImpl::UpdateCanBeApplied(
+    EvaluationContext* ec,
+    State* state,
+    std::string* error,
+    ErrorCode* result,
+    InstallPlan* install_plan) const {
+  bool interactive;
+  if (CheckInteractiveUpdateRequested(
+          ec, state->updater_provider(), &interactive)) {
+    LOG(INFO) << "Forced update signaled ("
+              << (interactive ? "interactive" : "periodic")
+              << "), allowing update to be applied.";
+    *result = ErrorCode::kSuccess;
+    return EvalStatus::kSucceeded;
+  }
+  return EvalStatus::kContinue;
+}
+
+bool InteractiveUpdatePolicyImpl::CheckInteractiveUpdateRequested(
+    EvaluationContext* ec,
+    UpdaterProvider* const updater_provider,
+    bool* interactive_out) const {
   // First, check to see if an interactive update was requested.
   const UpdateRequestStatus* forced_update_requested_p =
       ec->GetValue(updater_provider->var_forced_update_requested());
   if (forced_update_requested_p != nullptr &&
       *forced_update_requested_p != UpdateRequestStatus::kNone) {
-    result->interactive =
-        (*forced_update_requested_p == UpdateRequestStatus::kInteractive);
-    LOG(INFO) << "Forced update signaled ("
-              << (result->interactive ? "interactive" : "periodic")
-              << "), allowing update check.";
-    return EvalStatus::kSucceeded;
+    if (interactive_out)
+      *interactive_out =
+          (*forced_update_requested_p == UpdateRequestStatus::kInteractive);
+    return true;
   }
-  return EvalStatus::kContinue;
+  return false;
 }
 
 }  // namespace chromeos_update_manager
diff --git a/update_manager/interactive_update_policy_impl.h b/update_manager/interactive_update_policy_impl.h
index 18cf565..3690cfb 100644
--- a/update_manager/interactive_update_policy_impl.h
+++ b/update_manager/interactive_update_policy_impl.h
@@ -19,6 +19,8 @@
 
 #include <string>
 
+#include "update_engine/common/error_code.h"
+#include "update_engine/payload_consumer/install_plan.h"
 #include "update_engine/update_manager/policy_utils.h"
 
 namespace chromeos_update_manager {
@@ -35,12 +37,27 @@
                                 std::string* error,
                                 UpdateCheckParams* result) const override;
 
+  EvalStatus UpdateCanBeApplied(
+      EvaluationContext* ec,
+      State* state,
+      std::string* error,
+      chromeos_update_engine::ErrorCode* result,
+      chromeos_update_engine::InstallPlan* install_plan) const override;
+
  protected:
   std::string PolicyName() const override {
     return "InteractiveUpdatePolicyImpl";
   }
 
  private:
+  // Checks whether a forced update was requested. If there is a forced update,
+  // return true and set |interactive_out| to true if the forced update is
+  // interactive, and false otherwise. If there are no forced updates, return
+  // true and don't modify |interactive_out|.
+  bool CheckInteractiveUpdateRequested(EvaluationContext* ec,
+                                       UpdaterProvider* const updater_provider,
+                                       bool* interactive_out) const;
+
   DISALLOW_COPY_AND_ASSIGN(InteractiveUpdatePolicyImpl);
 };
 
diff --git a/update_manager/policy.h b/update_manager/policy.h
index f7a72d8..ee163b3 100644
--- a/update_manager/policy.h
+++ b/update_manager/policy.h
@@ -24,6 +24,7 @@
 #include "update_engine/common/error_code.h"
 #include "update_engine/payload_consumer/install_plan.h"
 #include "update_engine/update_manager/evaluation_context.h"
+#include "update_engine/update_manager/rollback_prefs.h"
 #include "update_engine/update_manager/state.h"
 
 namespace chromeos_update_manager {
@@ -47,6 +48,13 @@
   //
   // A target version prefix, if imposed by policy; otherwise, an empty string.
   std::string target_version_prefix;
+  // Specifies whether rollback images are allowed by device policy.
+  bool rollback_allowed;
+  // Specifies the number of Chrome milestones rollback should be allowed,
+  // starting from the stable version at any time. Value is -1 if unspecified
+  // (e.g. no device policy is available yet), in this case no version
+  // roll-forward should happen.
+  int rollback_allowed_milestones;
   // A target channel, if so imposed by policy; otherwise, an empty string.
   std::string target_channel;
 
diff --git a/update_manager/policy_test_utils.cc b/update_manager/policy_test_utils.cc
index d9a9857..5491e00 100644
--- a/update_manager/policy_test_utils.cc
+++ b/update_manager/policy_test_utils.cc
@@ -48,6 +48,17 @@
   fake_clock_.SetWallclockTime(Time::FromInternalValue(12345678901234L));
 }
 
+void UmPolicyTestBase::SetUpDefaultTimeProvider() {
+  Time current_time = fake_clock_.GetWallclockTime();
+  base::Time::Exploded exploded;
+  current_time.LocalExplode(&exploded);
+  fake_state_.time_provider()->var_curr_hour()->reset(new int(exploded.hour));
+  fake_state_.time_provider()->var_curr_minute()->reset(
+      new int(exploded.minute));
+  fake_state_.time_provider()->var_curr_date()->reset(
+      new Time(current_time.LocalMidnight()));
+}
+
 void UmPolicyTestBase::SetUpDefaultState() {
   fake_state_.updater_provider()->var_updater_started_time()->reset(
       new Time(fake_clock_.GetWallclockTime()));
diff --git a/update_manager/policy_test_utils.h b/update_manager/policy_test_utils.h
index 5b93f7b..eb5758f 100644
--- a/update_manager/policy_test_utils.h
+++ b/update_manager/policy_test_utils.h
@@ -42,6 +42,9 @@
   // Sets the clock to fixed values.
   virtual void SetUpDefaultClock();
 
+  // Sets the fake time provider to the time given by the fake clock.
+  virtual void SetUpDefaultTimeProvider();
+
   // Sets up the default state in fake_state_.  override to add Policy-specific
   // items, but only after calling this class's implementation.
   virtual void SetUpDefaultState();
diff --git a/update_manager/real_device_policy_provider.cc b/update_manager/real_device_policy_provider.cc
index d9880c3..e0872bb 100644
--- a/update_manager/real_device_policy_provider.cc
+++ b/update_manager/real_device_policy_provider.cc
@@ -18,6 +18,8 @@
 
 #include <stdint.h>
 
+#include <vector>
+
 #include <base/location.h>
 #include <base/logging.h>
 #include <base/time/time.h>
@@ -33,6 +35,7 @@
 using policy::DevicePolicy;
 using std::set;
 using std::string;
+using std::vector;
 
 namespace {
 
@@ -126,6 +129,23 @@
   }
 }
 
+bool RealDevicePolicyProvider::ConvertRollbackToTargetVersion(
+    RollbackToTargetVersion* rollback_to_target_version) const {
+  int rollback_to_target_version_int;
+  if (!policy_provider_->GetDevicePolicy().GetRollbackToTargetVersion(
+          &rollback_to_target_version_int)) {
+    return false;
+  }
+  if (rollback_to_target_version_int < 0 ||
+      rollback_to_target_version_int >=
+          static_cast<int>(RollbackToTargetVersion::kMaxValue)) {
+    return false;
+  }
+  *rollback_to_target_version =
+      static_cast<RollbackToTargetVersion>(rollback_to_target_version_int);
+  return true;
+}
+
 bool RealDevicePolicyProvider::ConvertAllowedConnectionTypesForUpdate(
       set<ConnectionType>* allowed_types) const {
   set<string> allowed_types_str;
@@ -162,6 +182,23 @@
   return true;
 }
 
+bool RealDevicePolicyProvider::ConvertDisallowedTimeIntervals(
+    WeeklyTimeIntervalVector* disallowed_intervals_out) const {
+  vector<DevicePolicy::WeeklyTimeInterval> parsed_intervals;
+  if (!policy_provider_->GetDevicePolicy().GetDisallowedTimeIntervals(
+          &parsed_intervals)) {
+    return false;
+  }
+
+  disallowed_intervals_out->clear();
+  for (const auto& interval : parsed_intervals) {
+    disallowed_intervals_out->emplace_back(
+        WeeklyTime(interval.start_day_of_week, interval.start_time),
+        WeeklyTime(interval.end_day_of_week, interval.end_time));
+  }
+  return true;
+}
+
 void RealDevicePolicyProvider::RefreshDevicePolicy() {
   if (!policy_provider_->Reload()) {
     LOG(INFO) << "No device policies/settings present.";
@@ -176,6 +213,14 @@
   UpdateVariable(&var_update_disabled_, &DevicePolicy::GetUpdateDisabled);
   UpdateVariable(&var_target_version_prefix_,
                  &DevicePolicy::GetTargetVersionPrefix);
+  UpdateVariable(&var_rollback_to_target_version_,
+                 &RealDevicePolicyProvider::ConvertRollbackToTargetVersion);
+  UpdateVariable(&var_rollback_allowed_milestones_,
+                 &DevicePolicy::GetRollbackAllowedMilestones);
+  if (policy_provider_->IsConsumerDevice()) {
+    // For consumer devices (which won't ever have policy), set value to 0.
+    var_rollback_allowed_milestones_.SetValue(0);
+  }
   UpdateVariable(&var_scatter_factor_,
                  &RealDevicePolicyProvider::ConvertScatterFactor);
   UpdateVariable(
@@ -187,6 +232,10 @@
   UpdateVariable(&var_au_p2p_enabled_, &DevicePolicy::GetAuP2PEnabled);
   UpdateVariable(&var_allow_kiosk_app_control_chrome_version_,
                  &DevicePolicy::GetAllowKioskAppControlChromeVersion);
+  UpdateVariable(&var_auto_launched_kiosk_app_id_,
+                 &DevicePolicy::GetAutoLaunchedKioskAppId);
+  UpdateVariable(&var_disallowed_time_intervals_,
+                 &RealDevicePolicyProvider::ConvertDisallowedTimeIntervals);
 }
 
 }  // namespace chromeos_update_manager
diff --git a/update_manager/real_device_policy_provider.h b/update_manager/real_device_policy_provider.h
index 5b5ee58..d999d81 100644
--- a/update_manager/real_device_policy_provider.h
+++ b/update_manager/real_device_policy_provider.h
@@ -20,6 +20,7 @@
 #include <memory>
 #include <set>
 #include <string>
+#include <utility>
 
 #include <brillo/message_loops/message_loop.h>
 #include <gtest/gtest_prod.h>  // for FRIEND_TEST
@@ -71,6 +72,14 @@
     return &var_target_version_prefix_;
   }
 
+  Variable<RollbackToTargetVersion>* var_rollback_to_target_version() override {
+    return &var_rollback_to_target_version_;
+  }
+
+  Variable<int>* var_rollback_allowed_milestones() override {
+    return &var_rollback_allowed_milestones_;
+  }
+
   Variable<base::TimeDelta>* var_scatter_factor() override {
     return &var_scatter_factor_;
   }
@@ -96,6 +105,14 @@
     return &var_allow_kiosk_app_control_chrome_version_;
   }
 
+  Variable<std::string>* var_auto_launched_kiosk_app_id() override {
+    return &var_auto_launched_kiosk_app_id_;
+  }
+
+  Variable<WeeklyTimeIntervalVector>* var_disallowed_time_intervals() override {
+    return &var_disallowed_time_intervals_;
+  }
+
  private:
   FRIEND_TEST(UmRealDevicePolicyProviderTest, RefreshScheduledTest);
   FRIEND_TEST(UmRealDevicePolicyProviderTest, NonExistentDevicePolicyReloaded);
@@ -130,6 +147,11 @@
       AsyncCopyVariable<T>* var,
       bool (RealDevicePolicyProvider::*getter_method)(T*) const);
 
+  // Wrapper for DevicePolicy::GetRollbackToTargetVersion() that converts the
+  // result to RollbackToTargetVersion.
+  bool ConvertRollbackToTargetVersion(
+      RollbackToTargetVersion* rollback_to_target_version) const;
+
   // Wrapper for DevicePolicy::GetScatterFactorInSeconds() that converts the
   // result to a base::TimeDelta. It returns the same value as
   // GetScatterFactorInSeconds().
@@ -140,6 +162,12 @@
   bool ConvertAllowedConnectionTypesForUpdate(
       std::set<chromeos_update_engine::ConnectionType>* allowed_types) const;
 
+  // Wrapper for DevicePolicy::GetUpdateTimeRestrictions() that converts
+  // the DevicePolicy::WeeklyTimeInterval structs to WeeklyTimeInterval objects,
+  // which offer more functionality.
+  bool ConvertDisallowedTimeIntervals(
+      WeeklyTimeIntervalVector* disallowed_intervals_out) const;
+
   // Used for fetching information about the device policy.
   policy::PolicyProvider* policy_provider_;
 
@@ -164,6 +192,10 @@
   AsyncCopyVariable<bool> var_update_disabled_{"update_disabled"};
   AsyncCopyVariable<std::string> var_target_version_prefix_{
       "target_version_prefix"};
+  AsyncCopyVariable<RollbackToTargetVersion> var_rollback_to_target_version_{
+      "rollback_to_target_version"};
+  AsyncCopyVariable<int> var_rollback_allowed_milestones_{
+      "rollback_allowed_milestones"};
   AsyncCopyVariable<base::TimeDelta> var_scatter_factor_{"scatter_factor"};
   AsyncCopyVariable<std::set<chromeos_update_engine::ConnectionType>>
       var_allowed_connection_types_for_update_{
@@ -173,6 +205,10 @@
   AsyncCopyVariable<bool> var_au_p2p_enabled_{"au_p2p_enabled"};
   AsyncCopyVariable<bool> var_allow_kiosk_app_control_chrome_version_{
       "allow_kiosk_app_control_chrome_version"};
+  AsyncCopyVariable<WeeklyTimeIntervalVector> var_disallowed_time_intervals_{
+      "update_time_restrictions"};
+  AsyncCopyVariable<std::string> var_auto_launched_kiosk_app_id_{
+      "auto_launched_kiosk_app_id"};
 
   DISALLOW_COPY_AND_ASSIGN(RealDevicePolicyProvider);
 };
diff --git a/update_manager/real_device_policy_provider_unittest.cc b/update_manager/real_device_policy_provider_unittest.cc
index 167cbd9..32e273d 100644
--- a/update_manager/real_device_policy_provider_unittest.cc
+++ b/update_manager/real_device_policy_provider_unittest.cc
@@ -17,6 +17,7 @@
 #include "update_engine/update_manager/real_device_policy_provider.h"
 
 #include <memory>
+#include <vector>
 
 #include <base/memory/ptr_util.h>
 #include <brillo/message_loops/fake_message_loop.h>
@@ -40,12 +41,14 @@
 using base::TimeDelta;
 using brillo::MessageLoop;
 using chromeos_update_engine::ConnectionType;
+using policy::DevicePolicy;
 #if USE_DBUS
 using chromeos_update_engine::dbus_test_utils::MockSignalHandler;
 #endif  // USE_DBUS
 using std::set;
 using std::string;
 using std::unique_ptr;
+using std::vector;
 using testing::DoAll;
 using testing::Mock;
 using testing::Return;
@@ -178,6 +181,10 @@
   UmTestUtils::ExpectVariableNotSet(provider_->var_release_channel_delegated());
   UmTestUtils::ExpectVariableNotSet(provider_->var_update_disabled());
   UmTestUtils::ExpectVariableNotSet(provider_->var_target_version_prefix());
+  UmTestUtils::ExpectVariableNotSet(
+      provider_->var_rollback_to_target_version());
+  UmTestUtils::ExpectVariableNotSet(
+      provider_->var_rollback_allowed_milestones());
   UmTestUtils::ExpectVariableNotSet(provider_->var_scatter_factor());
   UmTestUtils::ExpectVariableNotSet(
       provider_->var_allowed_connection_types_for_update());
@@ -186,6 +193,9 @@
   UmTestUtils::ExpectVariableNotSet(provider_->var_au_p2p_enabled());
   UmTestUtils::ExpectVariableNotSet(
       provider_->var_allow_kiosk_app_control_chrome_version());
+  UmTestUtils::ExpectVariableNotSet(
+      provider_->var_auto_launched_kiosk_app_id());
+  UmTestUtils::ExpectVariableNotSet(provider_->var_disallowed_time_intervals());
 }
 
 TEST_F(UmRealDevicePolicyProviderTest, ValuesUpdated) {
@@ -203,6 +213,8 @@
       .WillOnce(Return(false));
   EXPECT_CALL(mock_device_policy_, GetAllowKioskAppControlChromeVersion(_))
       .WillOnce(DoAll(SetArgPointee<0>(true), Return(true)));
+  EXPECT_CALL(mock_device_policy_, GetAutoLaunchedKioskAppId(_))
+      .WillOnce(DoAll(SetArgPointee<0>(string("myapp")), Return(true)));
 
   provider_->RefreshDevicePolicy();
 
@@ -216,6 +228,63 @@
       provider_->var_allowed_connection_types_for_update());
   UmTestUtils::ExpectVariableHasValue(
       true, provider_->var_allow_kiosk_app_control_chrome_version());
+  UmTestUtils::ExpectVariableHasValue(
+      string("myapp"), provider_->var_auto_launched_kiosk_app_id());
+}
+
+TEST_F(UmRealDevicePolicyProviderTest, RollbackToTargetVersionConverted) {
+  SetUpExistentDevicePolicy();
+  EXPECT_CALL(mock_device_policy_, GetRollbackToTargetVersion(_))
+#if USE_DBUS
+      .Times(2)
+#else
+      .Times(1)
+#endif  // USE_DBUS
+      .WillRepeatedly(DoAll(SetArgPointee<0>(2), Return(true)));
+  EXPECT_TRUE(provider_->Init());
+  loop_.RunOnce(false);
+
+  UmTestUtils::ExpectVariableHasValue(
+      RollbackToTargetVersion::kRollbackWithFullPowerwash,
+      provider_->var_rollback_to_target_version());
+}
+
+TEST_F(UmRealDevicePolicyProviderTest, RollbackAllowedMilestonesOobe) {
+  SetUpNonExistentDevicePolicy();
+  EXPECT_CALL(mock_device_policy_, GetRollbackAllowedMilestones(_)).Times(0);
+  ON_CALL(mock_policy_provider_, IsConsumerDevice())
+      .WillByDefault(Return(false));
+  EXPECT_TRUE(provider_->Init());
+  loop_.RunOnce(false);
+
+  UmTestUtils::ExpectVariableNotSet(
+      provider_->var_rollback_allowed_milestones());
+}
+
+TEST_F(UmRealDevicePolicyProviderTest, RollbackAllowedMilestonesConsumer) {
+  SetUpNonExistentDevicePolicy();
+  EXPECT_CALL(mock_device_policy_, GetRollbackAllowedMilestones(_)).Times(0);
+  ON_CALL(mock_policy_provider_, IsConsumerDevice())
+      .WillByDefault(Return(true));
+  EXPECT_TRUE(provider_->Init());
+  loop_.RunOnce(false);
+
+  UmTestUtils::ExpectVariableHasValue(
+      0, provider_->var_rollback_allowed_milestones());
+}
+
+TEST_F(UmRealDevicePolicyProviderTest,
+       RollbackAllowedMilestonesEnterprisePolicySet) {
+  SetUpExistentDevicePolicy();
+  ON_CALL(mock_device_policy_, GetRollbackAllowedMilestones(_))
+      .WillByDefault(DoAll(SetArgPointee<0>(2), Return(true)));
+  ON_CALL(mock_policy_provider_, IsConsumerDevice())
+      .WillByDefault(Return(false));
+  EXPECT_TRUE(provider_->Init());
+  loop_.RunOnce(false);
+
+  UmTestUtils::ExpectVariableHasValue(
+      2, provider_->var_rollback_allowed_milestones());
 }
 
 TEST_F(UmRealDevicePolicyProviderTest, ScatterFactorConverted) {
@@ -268,4 +337,25 @@
       provider_->var_allowed_connection_types_for_update());
 }
 
+TEST_F(UmRealDevicePolicyProviderTest, DisallowedIntervalsConverted) {
+  SetUpExistentDevicePolicy();
+
+  vector<DevicePolicy::WeeklyTimeInterval> intervals = {
+      {5, TimeDelta::FromHours(5), 6, TimeDelta::FromHours(8)},
+      {1, TimeDelta::FromHours(1), 3, TimeDelta::FromHours(10)}};
+
+  EXPECT_CALL(mock_device_policy_, GetDisallowedTimeIntervals(_))
+      .WillRepeatedly(DoAll(SetArgPointee<0>(intervals), Return(true)));
+  EXPECT_TRUE(provider_->Init());
+  loop_.RunOnce(false);
+
+  UmTestUtils::ExpectVariableHasValue(
+      WeeklyTimeIntervalVector{
+          WeeklyTimeInterval(WeeklyTime(5, TimeDelta::FromHours(5)),
+                             WeeklyTime(6, TimeDelta::FromHours(8))),
+          WeeklyTimeInterval(WeeklyTime(1, TimeDelta::FromHours(1)),
+                             WeeklyTime(3, TimeDelta::FromHours(10)))},
+      provider_->var_disallowed_time_intervals());
+}
+
 }  // namespace chromeos_update_manager
diff --git a/update_manager/real_system_provider.cc b/update_manager/real_system_provider.cc
index fdf7e86..53e9ab3 100644
--- a/update_manager/real_system_provider.cc
+++ b/update_manager/real_system_provider.cc
@@ -21,7 +21,7 @@
 #include <base/logging.h>
 #include <base/time/time.h>
 #if USE_CHROME_KIOSK_APP
-#include <libcros/dbus-proxies.h>
+#include <kiosk-app/dbus-proxies.h>
 #endif  // USE_CHROME_KIOSK_APP
 
 #include "update_engine/common/utils.h"
@@ -126,8 +126,8 @@
     string* required_platform_version) {
 #if USE_CHROME_KIOSK_APP
   brillo::ErrorPtr error;
-  if (!libcros_proxy_->GetKioskAppRequiredPlatformVersion(
-          required_platform_version, &error)) {
+  if (!kiosk_app_proxy_->GetRequiredPlatformVersion(required_platform_version,
+                                                    &error)) {
     LOG(WARNING) << "Failed to get kiosk required platform version";
     required_platform_version->clear();
     return false;
diff --git a/update_manager/real_system_provider.h b/update_manager/real_system_provider.h
index 80a8615..9d71d0d 100644
--- a/update_manager/real_system_provider.h
+++ b/update_manager/real_system_provider.h
@@ -26,7 +26,7 @@
 
 namespace org {
 namespace chromium {
-class LibCrosServiceInterfaceProxyInterface;
+class KioskAppServiceInterfaceProxyInterface;
 }  // namespace chromium
 }  // namespace org
 
@@ -38,11 +38,12 @@
   RealSystemProvider(
       chromeos_update_engine::HardwareInterface* hardware,
       chromeos_update_engine::BootControlInterface* boot_control,
-      org::chromium::LibCrosServiceInterfaceProxyInterface* libcros_proxy)
+      org::chromium::KioskAppServiceInterfaceProxyInterface* kiosk_app_proxy)
       : hardware_(hardware),
 #if USE_CHROME_KIOSK_APP
         boot_control_(boot_control),
-        libcros_proxy_(libcros_proxy) {}
+        kiosk_app_proxy_(kiosk_app_proxy) {
+  }
 #else
         boot_control_(boot_control) {}
 #endif  // USE_CHROME_KIOSK_APP
@@ -83,7 +84,7 @@
   chromeos_update_engine::HardwareInterface* const hardware_;
   chromeos_update_engine::BootControlInterface* const boot_control_;
 #if USE_CHROME_KIOSK_APP
-  org::chromium::LibCrosServiceInterfaceProxyInterface* const libcros_proxy_;
+  org::chromium::KioskAppServiceInterfaceProxyInterface* const kiosk_app_proxy_;
 #endif  // USE_CHROME_KIOSK_APP
 
   DISALLOW_COPY_AND_ASSIGN(RealSystemProvider);
diff --git a/update_manager/real_system_provider_unittest.cc b/update_manager/real_system_provider_unittest.cc
index 103a35f..4e4da67 100644
--- a/update_manager/real_system_provider_unittest.cc
+++ b/update_manager/real_system_provider_unittest.cc
@@ -26,10 +26,10 @@
 #include "update_engine/common/fake_hardware.h"
 #include "update_engine/update_manager/umtest_utils.h"
 #if USE_CHROME_KIOSK_APP
-#include "libcros/dbus-proxies.h"
-#include "libcros/dbus-proxy-mocks.h"
+#include "kiosk-app/dbus-proxies.h"
+#include "kiosk-app/dbus-proxy-mocks.h"
 
-using org::chromium::LibCrosServiceInterfaceProxyMock;
+using org::chromium::KioskAppServiceInterfaceProxyMock;
 #endif  // USE_CHROME_KIOSK_APP
 using std::unique_ptr;
 using testing::_;
@@ -49,14 +49,13 @@
  protected:
   void SetUp() override {
 #if USE_CHROME_KIOSK_APP
-    libcros_proxy_mock_.reset(new LibCrosServiceInterfaceProxyMock());
-    ON_CALL(*libcros_proxy_mock_,
-            GetKioskAppRequiredPlatformVersion(_, _, _))
+    kiosk_app_proxy_mock_.reset(new KioskAppServiceInterfaceProxyMock());
+    ON_CALL(*kiosk_app_proxy_mock_, GetRequiredPlatformVersion(_, _, _))
         .WillByDefault(
             DoAll(SetArgPointee<0>(kRequiredPlatformVersion), Return(true)));
 
     provider_.reset(new RealSystemProvider(
-        &fake_hardware_, &fake_boot_control_, libcros_proxy_mock_.get()));
+        &fake_hardware_, &fake_boot_control_, kiosk_app_proxy_mock_.get()));
 #else
     provider_.reset(
         new RealSystemProvider(&fake_hardware_, &fake_boot_control_, nullptr));
@@ -69,7 +68,7 @@
   unique_ptr<RealSystemProvider> provider_;
 
 #if USE_CHROME_KIOSK_APP
-  unique_ptr<LibCrosServiceInterfaceProxyMock> libcros_proxy_mock_;
+  unique_ptr<KioskAppServiceInterfaceProxyMock> kiosk_app_proxy_mock_;
 #endif  // USE_CHROME_KIOSK_APP
 };
 
@@ -98,8 +97,7 @@
 }
 
 TEST_F(UmRealSystemProviderTest, KioskRequiredPlatformVersionFailure) {
-  EXPECT_CALL(*libcros_proxy_mock_,
-              GetKioskAppRequiredPlatformVersion(_, _, _))
+  EXPECT_CALL(*kiosk_app_proxy_mock_, GetRequiredPlatformVersion(_, _, _))
       .WillOnce(Return(false));
 
   UmTestUtils::ExpectVariableNotSet(
@@ -108,15 +106,13 @@
 
 TEST_F(UmRealSystemProviderTest,
        KioskRequiredPlatformVersionRecoveryFromFailure) {
-  EXPECT_CALL(*libcros_proxy_mock_,
-              GetKioskAppRequiredPlatformVersion(_, _, _))
+  EXPECT_CALL(*kiosk_app_proxy_mock_, GetRequiredPlatformVersion(_, _, _))
       .WillOnce(Return(false));
   UmTestUtils::ExpectVariableNotSet(
       provider_->var_kiosk_required_platform_version());
-  testing::Mock::VerifyAndClearExpectations(libcros_proxy_mock_.get());
+  testing::Mock::VerifyAndClearExpectations(kiosk_app_proxy_mock_.get());
 
-  EXPECT_CALL(*libcros_proxy_mock_,
-              GetKioskAppRequiredPlatformVersion(_, _, _))
+  EXPECT_CALL(*kiosk_app_proxy_mock_, GetRequiredPlatformVersion(_, _, _))
       .WillOnce(
           DoAll(SetArgPointee<0>(kRequiredPlatformVersion), Return(true)));
   UmTestUtils::ExpectVariableHasValue(
diff --git a/update_manager/real_time_provider.cc b/update_manager/real_time_provider.cc
index db26816..baa8ae3 100644
--- a/update_manager/real_time_provider.cc
+++ b/update_manager/real_time_provider.cc
@@ -77,9 +77,28 @@
   DISALLOW_COPY_AND_ASSIGN(CurrHourVariable);
 };
 
+class CurrMinuteVariable : public Variable<int> {
+ public:
+  CurrMinuteVariable(const string& name, ClockInterface* clock)
+      : Variable<int>(name, TimeDelta::FromSeconds(15)), clock_(clock) {}
+
+ protected:
+  virtual const int* GetValue(TimeDelta /* timeout */, string* /* errmsg */) {
+    Time::Exploded exploded;
+    clock_->GetWallclockTime().LocalExplode(&exploded);
+    return new int(exploded.minute);
+  }
+
+ private:
+  ClockInterface* clock_;
+
+  DISALLOW_COPY_AND_ASSIGN(CurrMinuteVariable);
+};
+
 bool RealTimeProvider::Init() {
   var_curr_date_.reset(new CurrDateVariable("curr_date", clock_));
   var_curr_hour_.reset(new CurrHourVariable("curr_hour", clock_));
+  var_curr_minute_.reset(new CurrMinuteVariable("curr_minute", clock_));
   return true;
 }
 
diff --git a/update_manager/real_time_provider.h b/update_manager/real_time_provider.h
index e7cae94..989cefb 100644
--- a/update_manager/real_time_provider.h
+++ b/update_manager/real_time_provider.h
@@ -43,12 +43,15 @@
     return var_curr_hour_.get();
   }
 
+  Variable<int>* var_curr_minute() override { return var_curr_minute_.get(); }
+
  private:
   // A clock abstraction (fakeable).
   chromeos_update_engine::ClockInterface* const clock_;
 
   std::unique_ptr<Variable<base::Time>> var_curr_date_;
   std::unique_ptr<Variable<int>> var_curr_hour_;
+  std::unique_ptr<Variable<int>> var_curr_minute_;
 
   DISALLOW_COPY_AND_ASSIGN(RealTimeProvider);
 };
diff --git a/update_manager/real_time_provider_unittest.cc b/update_manager/real_time_provider_unittest.cc
index f8db30b..ce2a718 100644
--- a/update_manager/real_time_provider_unittest.cc
+++ b/update_manager/real_time_provider_unittest.cc
@@ -84,4 +84,13 @@
                                       provider_->var_curr_hour());
 }
 
+TEST_F(UmRealTimeProviderTest, CurrMinuteValid) {
+  const Time now = CurrTime();
+  Time::Exploded expected;
+  now.LocalExplode(&expected);
+  fake_clock_.SetWallclockTime(now);
+  UmTestUtils::ExpectVariableHasValue(expected.minute,
+                                      provider_->var_curr_minute());
+}
+
 }  // namespace chromeos_update_manager
diff --git a/update_manager/rollback_prefs.h b/update_manager/rollback_prefs.h
new file mode 100644
index 0000000..1783eb0
--- /dev/null
+++ b/update_manager/rollback_prefs.h
@@ -0,0 +1,39 @@
+//
+// Copyright (C) 2018 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_ROLLBACK_PREFS_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_ROLLBACK_PREFS_H_
+
+namespace chromeos_update_manager {
+
+// Value used to represent that kernel key versions can always roll-forward.
+// This is the maximum value of a kernel key version.
+constexpr int kRollforwardInfinity = 0xfffffffe;
+
+// Whether the device should roll back to the target version, and if yes, which
+// type of rollback should it do. Matches chrome_device_policy.proto's
+// AutoUpdateSettingsProto::RollbackToTargetVersion.
+enum class RollbackToTargetVersion {
+  kUnspecified = 0,
+  kDisabled = 1,
+  kRollbackWithFullPowerwash = 2,
+  // This value must be the last entry.
+  kMaxValue = 3
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_ROLLBACK_PREFS_H_
diff --git a/update_manager/staging_utils.cc b/update_manager/staging_utils.cc
new file mode 100644
index 0000000..4835ab2
--- /dev/null
+++ b/update_manager/staging_utils.cc
@@ -0,0 +1,142 @@
+//
+// Copyright (C) 2018 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/staging_utils.h"
+
+#include <utility>
+#include <vector>
+
+#include <base/logging.h>
+#include <base/rand_util.h>
+#include <base/time/time.h>
+#include <policy/device_policy.h>
+
+#include "update_engine/common/constants.h"
+#include "update_engine/common/hardware_interface.h"
+#include "update_engine/common/prefs_interface.h"
+#include "update_engine/system_state.h"
+
+using base::TimeDelta;
+using chromeos_update_engine::kPrefsWallClockStagingWaitPeriod;
+using chromeos_update_engine::PrefsInterface;
+using chromeos_update_engine::SystemState;
+using policy::DevicePolicy;
+
+namespace chromeos_update_manager {
+
+int GetStagingSchedule(const DevicePolicy* device_policy,
+                       StagingSchedule* staging_schedule_out) {
+  StagingSchedule staging_schedule;
+  if (!device_policy->GetDeviceUpdateStagingSchedule(&staging_schedule) ||
+      staging_schedule.empty()) {
+    return 0;
+  }
+
+  // Last percentage of the schedule should be 100.
+  if (staging_schedule.back().percentage != 100) {
+    LOG(ERROR) << "Last percentage of the schedule is not 100, it's: "
+               << staging_schedule.back().percentage;
+    return 0;
+  }
+
+  int previous_days = 0;
+  int previous_percentage = -1;
+  // Ensure that the schedule has a monotonically increasing set of percentages
+  // and that days are also monotonically increasing.
+  for (const auto& staging_pair : staging_schedule) {
+    int days = staging_pair.days;
+    if (previous_days >= days) {
+      LOG(ERROR) << "Days in staging schedule are not monotonically "
+                 << "increasing. Previous value: " << previous_days
+                 << " Current value: " << days;
+      return 0;
+    }
+    previous_days = days;
+    int percentage = staging_pair.percentage;
+    if (previous_percentage >= percentage) {
+      LOG(ERROR) << "Percentages in staging schedule are not monotonically "
+                 << "increasing. Previous value: " << previous_percentage
+                 << " Current value: " << percentage;
+      return 0;
+    }
+    previous_percentage = percentage;
+  }
+  // Modify staging schedule only if the schedule in the device policy is valid.
+  if (staging_schedule_out)
+    *staging_schedule_out = std::move(staging_schedule);
+
+  return previous_days;
+}
+
+int CalculateWaitTimeInDaysFromSchedule(
+    const StagingSchedule& staging_schedule) {
+  int prev_days = 0;
+  int percentage_position = base::RandInt(1, 100);
+  for (const auto& staging_pair : staging_schedule) {
+    int days = staging_pair.days;
+    if (percentage_position <= staging_pair.percentage) {
+      // Scatter between the start of the range and the end.
+      return prev_days + base::RandInt(1, days - prev_days);
+    }
+    prev_days = days;
+  }
+  // Something went wrong.
+  NOTREACHED();
+  return 0;
+}
+
+StagingCase CalculateStagingCase(const DevicePolicy* device_policy,
+                                 PrefsInterface* prefs,
+                                 TimeDelta* staging_wait_time,
+                                 StagingSchedule* staging_schedule) {
+  // Check that the schedule in the device policy is correct.
+  StagingSchedule new_staging_schedule;
+  int max_days = GetStagingSchedule(device_policy, &new_staging_schedule);
+  if (max_days == 0)
+    return StagingCase::kOff;
+
+  // Calculate the new wait time.
+  TimeDelta new_staging_wait_time = TimeDelta::FromDays(
+      CalculateWaitTimeInDaysFromSchedule(new_staging_schedule));
+  DCHECK_GT(new_staging_wait_time.InSeconds(), 0);
+  if (staging_wait_time->InSeconds() > 0) {
+    // If there hasn't been any changes to the schedule and there is a value
+    // set, don't change the waiting time.
+    if (new_staging_schedule == *staging_schedule) {
+      return StagingCase::kNoAction;
+    }
+    // Otherwise, update the schedule and wait time.
+    *staging_wait_time = new_staging_wait_time;
+    *staging_schedule = std::move(new_staging_schedule);
+    return StagingCase::kNoSavedValue;
+  }
+  // Getting this means the schedule changed, update the old schedule.
+  *staging_schedule = std::move(new_staging_schedule);
+
+  int64_t wait_period_in_days;
+  // There exists a persisted value that is valid. That is, it's smaller than
+  // the maximum amount of days of staging set by the user.
+  if (prefs->GetInt64(kPrefsWallClockStagingWaitPeriod, &wait_period_in_days) &&
+      wait_period_in_days > 0 && wait_period_in_days <= max_days) {
+    *staging_wait_time = TimeDelta::FromDays(wait_period_in_days);
+    return StagingCase::kSetStagingFromPref;
+  }
+
+  *staging_wait_time = new_staging_wait_time;
+  return StagingCase::kNoSavedValue;
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/staging_utils.h b/update_manager/staging_utils.h
new file mode 100644
index 0000000..e91bfeb
--- /dev/null
+++ b/update_manager/staging_utils.h
@@ -0,0 +1,71 @@
+//
+// Copyright (C) 2018 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_STAGING_UTILS_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_STAGING_UTILS_H_
+
+#include <utility>
+#include <vector>
+
+#include <base/time/time.h>
+#include <policy/device_policy.h>
+
+#include "update_engine/common/prefs_interface.h"
+
+namespace chromeos_update_manager {
+
+using StagingSchedule = std::vector<policy::DevicePolicy::DayPercentagePair>;
+
+// Possible cases that staging might run into based on the inputs.
+enum class StagingCase {
+  // Staging is off, remove the persisted value.
+  kOff,
+  // Staging is enabled, but there is no valid persisted value, saved value or
+  // the value of the schedule has changed.
+  kNoSavedValue,
+  // Staging is enabled, and there is a valid persisted value.
+  kSetStagingFromPref,
+  // Staging is enabled, and there have been no changes to the schedule.
+  kNoAction
+};
+
+// Calculate the bucket in which the device belongs based on a given staging
+// schedule. |staging_schedule| is assumed to have already been validated.
+int CalculateWaitTimeInDaysFromSchedule(
+    const StagingSchedule& staging_schedule);
+
+// Verifies that |device_policy| contains a valid staging schedule. If
+// |device_policy| contains a valid staging schedule, move it into
+// |staging_schedule_out| and return the total number of days spanned by the
+// schedule. Otherwise, don't modify |staging_schedule_out| and return 0 (which
+// is an invalid value for the length of a schedule).
+int GetStagingSchedule(const policy::DevicePolicy* device_policy,
+                       StagingSchedule* staging_schedule_out);
+
+// Uses the given arguments to check whether staging is on, and whether the
+// state should be updated with a new waiting time or not. |staging_wait_time|
+// should contain the old value of the wait time, it will be replaced with the
+// new calculated wait time value if staging is on. |staging_schedule| should
+// contain the previous staging schedule, if there is a new schedule found, its
+// value will be replaced with the new one.
+StagingCase CalculateStagingCase(const policy::DevicePolicy* device_policy,
+                                 chromeos_update_engine::PrefsInterface* prefs,
+                                 base::TimeDelta* staging_wait_time,
+                                 StagingSchedule* staging_schedule);
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_STAGING_UTILS_H_
diff --git a/update_manager/staging_utils_unittest.cc b/update_manager/staging_utils_unittest.cc
new file mode 100644
index 0000000..8d75acd
--- /dev/null
+++ b/update_manager/staging_utils_unittest.cc
@@ -0,0 +1,175 @@
+//
+// Copyright (C) 2018 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/staging_utils.h"
+
+#include <memory>
+#include <utility>
+
+#include <base/time/time.h>
+#include <gtest/gtest.h>
+#include <policy/mock_device_policy.h>
+
+#include "update_engine/common/constants.h"
+#include "update_engine/common/fake_prefs.h"
+
+using base::TimeDelta;
+using chromeos_update_engine::FakePrefs;
+using chromeos_update_engine::kPrefsWallClockStagingWaitPeriod;
+using testing::_;
+using testing::DoAll;
+using testing::Return;
+using testing::SetArgPointee;
+
+namespace chromeos_update_manager {
+
+constexpr TimeDelta kDay = TimeDelta::FromDays(1);
+constexpr int kMaxDays = 28;
+constexpr int kValidDaySum = 14;
+const StagingSchedule valid_schedule = {{2, 0}, {7, 50}, {9, 80}, {14, 100}};
+
+class StagingUtilsScheduleTest : public testing::Test {
+ protected:
+  void SetUp() override {
+    test_wait_time_ = TimeDelta();
+    test_staging_schedule_ = StagingSchedule();
+  }
+
+  void SetStagingSchedule(const StagingSchedule& staging_schedule) {
+    EXPECT_CALL(device_policy_, GetDeviceUpdateStagingSchedule(_))
+        .WillRepeatedly(
+            DoAll(SetArgPointee<0>(staging_schedule), Return(true)));
+  }
+
+  void SetPersistedStagingVal(int64_t wait_time) {
+    EXPECT_TRUE(
+        fake_prefs_.SetInt64(kPrefsWallClockStagingWaitPeriod, wait_time));
+  }
+
+  void TestStagingCase(const StagingCase& expected) {
+    EXPECT_EQ(expected,
+              CalculateStagingCase(&device_policy_,
+                                   &fake_prefs_,
+                                   &test_wait_time_,
+                                   &test_staging_schedule_));
+  }
+
+  void ExpectNoChanges() {
+    EXPECT_EQ(TimeDelta(), test_wait_time_);
+    EXPECT_EQ(StagingSchedule(), test_staging_schedule_);
+  }
+
+  policy::MockDevicePolicy device_policy_;
+  TimeDelta test_wait_time_;
+  StagingSchedule test_staging_schedule_;
+  FakePrefs fake_prefs_;
+};
+
+// Last element should be 100, if not return false.
+TEST_F(StagingUtilsScheduleTest, GetStagingScheduleInvalidLastElem) {
+  SetStagingSchedule(StagingSchedule{{2, 10}, {4, 20}, {5, 40}});
+  EXPECT_EQ(0, GetStagingSchedule(&device_policy_, &test_staging_schedule_));
+  ExpectNoChanges();
+}
+
+// Percentage should be monotonically increasing.
+TEST_F(StagingUtilsScheduleTest, GetStagingScheduleNonMonotonic) {
+  SetStagingSchedule(StagingSchedule{{2, 10}, {6, 20}, {11, 20}, {12, 100}});
+  EXPECT_EQ(0, GetStagingSchedule(&device_policy_, &test_staging_schedule_));
+  ExpectNoChanges();
+}
+
+// The days should be monotonically increasing.
+TEST_F(StagingUtilsScheduleTest, GetStagingScheduleOverMaxDays) {
+  SetStagingSchedule(StagingSchedule{{2, 10}, {4, 20}, {15, 30}, {10, 100}});
+  EXPECT_EQ(0, GetStagingSchedule(&device_policy_, &test_staging_schedule_));
+  ExpectNoChanges();
+}
+
+TEST_F(StagingUtilsScheduleTest, GetStagingScheduleValid) {
+  SetStagingSchedule(valid_schedule);
+  EXPECT_EQ(kValidDaySum,
+            GetStagingSchedule(&device_policy_, &test_staging_schedule_));
+  EXPECT_EQ(test_staging_schedule_, valid_schedule);
+}
+
+TEST_F(StagingUtilsScheduleTest, StagingOffNoSchedule) {
+  // If the function returns false, the schedule shouldn't get used.
+  EXPECT_CALL(device_policy_, GetDeviceUpdateStagingSchedule(_))
+      .WillRepeatedly(DoAll(SetArgPointee<0>(valid_schedule), Return(false)));
+  TestStagingCase(StagingCase::kOff);
+  ExpectNoChanges();
+}
+
+TEST_F(StagingUtilsScheduleTest, StagingOffEmptySchedule) {
+  SetStagingSchedule(StagingSchedule());
+  TestStagingCase(StagingCase::kOff);
+  ExpectNoChanges();
+}
+
+TEST_F(StagingUtilsScheduleTest, StagingOffInvalidSchedule) {
+  // Any invalid schedule should return |StagingCase::kOff|.
+  SetStagingSchedule(StagingSchedule{{3, 30}, {6, 40}});
+  TestStagingCase(StagingCase::kOff);
+  ExpectNoChanges();
+}
+
+TEST_F(StagingUtilsScheduleTest, StagingOnNoAction) {
+  test_wait_time_ = kDay;
+  // Same as valid schedule, just using std::pair types.
+  StagingSchedule valid_schedule_pairs = {{2, 0}, {7, 50}, {9, 80}, {14, 100}};
+  test_staging_schedule_ = valid_schedule_pairs;
+  SetStagingSchedule(valid_schedule);
+  TestStagingCase(StagingCase::kNoAction);
+  // Vars should not be changed.
+  EXPECT_EQ(kDay, test_wait_time_);
+  EXPECT_EQ(test_staging_schedule_, valid_schedule_pairs);
+}
+
+TEST_F(StagingUtilsScheduleTest, StagingNoSavedValueChangePolicy) {
+  test_wait_time_ = kDay;
+  SetStagingSchedule(valid_schedule);
+  TestStagingCase(StagingCase::kNoSavedValue);
+  // Vars should change since < 2 days should not be possible due to
+  // valid_schedule's value.
+  EXPECT_NE(kDay, test_wait_time_);
+  EXPECT_EQ(test_staging_schedule_, valid_schedule);
+  EXPECT_LE(test_wait_time_, kDay * kMaxDays);
+}
+
+// Tests the case where there was a reboot and there is no persisted value.
+TEST_F(StagingUtilsScheduleTest, StagingNoSavedValueNoPersisted) {
+  SetStagingSchedule(valid_schedule);
+  TestStagingCase(StagingCase::kNoSavedValue);
+  // Vars should change since there are no preset values and there is a new
+  // staging schedule.
+  EXPECT_NE(TimeDelta(), test_wait_time_);
+  EXPECT_EQ(test_staging_schedule_, valid_schedule);
+  EXPECT_LE(test_wait_time_, kDay * kMaxDays);
+}
+
+// If there is a pref set and its value is less than the day count, use that
+// pref.
+TEST_F(StagingUtilsScheduleTest, StagingSetFromPref) {
+  SetStagingSchedule(valid_schedule);
+  SetPersistedStagingVal(5);
+  TestStagingCase(StagingCase::kSetStagingFromPref);
+  // Vars should change.
+  EXPECT_EQ(kDay * 5, test_wait_time_);
+  EXPECT_EQ(test_staging_schedule_, valid_schedule);
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/state_factory.cc b/update_manager/state_factory.cc
index 208ed51..7293692 100644
--- a/update_manager/state_factory.cc
+++ b/update_manager/state_factory.cc
@@ -46,7 +46,7 @@
 
 State* DefaultStateFactory(
     policy::PolicyProvider* policy_provider,
-    org::chromium::LibCrosServiceInterfaceProxyInterface* libcros_proxy,
+    org::chromium::KioskAppServiceInterfaceProxyInterface* kiosk_app_proxy,
     chromeos_update_engine::SystemState* system_state) {
   chromeos_update_engine::ClockInterface* const clock = system_state->clock();
   unique_ptr<RealConfigProvider> config_provider(
@@ -70,7 +70,7 @@
 #endif  // USE_SHILL
   unique_ptr<RealRandomProvider> random_provider(new RealRandomProvider());
   unique_ptr<RealSystemProvider> system_provider(new RealSystemProvider(
-      system_state->hardware(), system_state->boot_control(), libcros_proxy));
+      system_state->hardware(), system_state->boot_control(), kiosk_app_proxy));
 
   unique_ptr<RealTimeProvider> time_provider(new RealTimeProvider(clock));
   unique_ptr<RealUpdaterProvider> updater_provider(
diff --git a/update_manager/state_factory.h b/update_manager/state_factory.h
index 689684a..1c1c1d9 100644
--- a/update_manager/state_factory.h
+++ b/update_manager/state_factory.h
@@ -22,7 +22,7 @@
 
 namespace org {
 namespace chromium {
-class LibCrosServiceInterfaceProxyInterface;
+class KioskAppServiceInterfaceProxyInterface;
 }  // namespace chromium
 }  // namespace org
 
@@ -35,7 +35,7 @@
 // to initialize.
 State* DefaultStateFactory(
     policy::PolicyProvider* policy_provider,
-    org::chromium::LibCrosServiceInterfaceProxyInterface* libcros_proxy,
+    org::chromium::KioskAppServiceInterfaceProxyInterface* kiosk_app_proxy,
     chromeos_update_engine::SystemState* system_state);
 
 }  // namespace chromeos_update_manager
diff --git a/update_manager/time_provider.h b/update_manager/time_provider.h
index 663ec2c..94f4a8f 100644
--- a/update_manager/time_provider.h
+++ b/update_manager/time_provider.h
@@ -36,6 +36,9 @@
   // consistent with base::Time.
   virtual Variable<int>* var_curr_hour() = 0;
 
+  // Returns the current minutes (0 to 60) in local time.
+  virtual Variable<int>* var_curr_minute() = 0;
+
  protected:
   TimeProvider() {}
 
diff --git a/update_manager/update_time_restrictions_policy_impl.cc b/update_manager/update_time_restrictions_policy_impl.cc
new file mode 100644
index 0000000..f9b83de
--- /dev/null
+++ b/update_manager/update_time_restrictions_policy_impl.cc
@@ -0,0 +1,74 @@
+//
+// Copyright (C) 2018 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/update_time_restrictions_policy_impl.h"
+
+#include <memory>
+
+#include <base/time/time.h>
+
+#include "update_engine/update_manager/device_policy_provider.h"
+#include "update_engine/update_manager/system_provider.h"
+#include "update_engine/update_manager/weekly_time.h"
+
+using base::Time;
+using base::TimeDelta;
+
+using chromeos_update_engine::ErrorCode;
+using chromeos_update_engine::InstallPlan;
+
+namespace chromeos_update_manager {
+
+EvalStatus UpdateTimeRestrictionsPolicyImpl::UpdateCanBeApplied(
+    EvaluationContext* ec,
+    State* state,
+    std::string* error,
+    ErrorCode* result,
+    InstallPlan* install_plan) const {
+  DevicePolicyProvider* const dp_provider = state->device_policy_provider();
+  TimeProvider* const time_provider = state->time_provider();
+
+  // If kiosk mode is not enabled, don't restrict updates.
+  if (!ec->GetValue(dp_provider->var_auto_launched_kiosk_app_id()))
+    return EvalStatus::kContinue;
+
+  const Time* curr_date = ec->GetValue(time_provider->var_curr_date());
+  const int* curr_hour = ec->GetValue(time_provider->var_curr_hour());
+  const int* curr_minute = ec->GetValue(time_provider->var_curr_minute());
+  if (!curr_date || !curr_hour || !curr_minute) {
+    LOG(WARNING) << "Unable to access local time.";
+    return EvalStatus::kContinue;
+  }
+
+  WeeklyTime now = WeeklyTime::FromTime(*curr_date);
+  now.AddTime(TimeDelta::FromHours(*curr_hour) +
+              TimeDelta::FromMinutes(*curr_minute));
+
+  const WeeklyTimeIntervalVector* intervals =
+      ec->GetValue(dp_provider->var_disallowed_time_intervals());
+  if (!intervals) {
+    return EvalStatus::kContinue;
+  }
+  for (const auto& interval : *intervals) {
+    if (interval.InRange(now)) {
+      *result = ErrorCode::kOmahaUpdateDeferredPerPolicy;
+      return EvalStatus::kSucceeded;
+    }
+  }
+
+  return EvalStatus::kContinue;
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/update_time_restrictions_policy_impl.h b/update_manager/update_time_restrictions_policy_impl.h
new file mode 100644
index 0000000..11cbceb
--- /dev/null
+++ b/update_manager/update_time_restrictions_policy_impl.h
@@ -0,0 +1,61 @@
+//
+// Copyright (C) 2018 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_UPDATE_TIME_RESTRICTIONS_POLICY_IMPL_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_UPDATE_TIME_RESTRICTIONS_POLICY_IMPL_H_
+
+#include <string>
+
+#include <base/time/time.h>
+
+#include "update_engine/common/error_code.h"
+#include "update_engine/payload_consumer/install_plan.h"
+#include "update_engine/update_manager/policy_utils.h"
+
+namespace chromeos_update_manager {
+
+// Policy that allows administrators to set time intervals during which
+// automatic update checks are disallowed. This implementation then checks if
+// the current time falls in the range spanned by the time intervals. If the
+// current time falls in one of the intervals then the update check is
+// blocked by this policy.
+class UpdateTimeRestrictionsPolicyImpl : public PolicyImplBase {
+ public:
+  UpdateTimeRestrictionsPolicyImpl() = default;
+  ~UpdateTimeRestrictionsPolicyImpl() override = default;
+
+  // When the current time is inside one of the intervals returns
+  // kSucceeded and sets |result| to kOmahaUpdateDeferredPerPolicy. If the
+  // current time is not inside any intervals returns kContinue. In case of
+  // errors, i.e. cannot access intervals or time, return kContinue.
+  EvalStatus UpdateCanBeApplied(
+      EvaluationContext* ec,
+      State* state,
+      std::string* error,
+      chromeos_update_engine::ErrorCode* result,
+      chromeos_update_engine::InstallPlan* install_plan) const override;
+
+ protected:
+  std::string PolicyName() const override {
+    return "UpdateTimeRestrictionsPolicyImpl";
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(UpdateTimeRestrictionsPolicyImpl);
+};
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_UPDATE_TIME_RESTRICTIONS_POLICY_IMPL_H_
diff --git a/update_manager/update_time_restrictions_policy_impl_unittest.cc b/update_manager/update_time_restrictions_policy_impl_unittest.cc
new file mode 100644
index 0000000..74e7f3c
--- /dev/null
+++ b/update_manager/update_time_restrictions_policy_impl_unittest.cc
@@ -0,0 +1,120 @@
+//
+// Copyright (C) 2018 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/update_time_restrictions_policy_impl.h"
+
+#include <memory>
+
+#include <base/time/time.h>
+
+#include "update_engine/update_manager/policy_test_utils.h"
+#include "update_engine/update_manager/weekly_time.h"
+
+using base::Time;
+using base::TimeDelta;
+using chromeos_update_engine::ErrorCode;
+using chromeos_update_engine::InstallPlan;
+using std::string;
+
+namespace chromeos_update_manager {
+
+constexpr TimeDelta kHour = TimeDelta::FromHours(1);
+constexpr TimeDelta kMinute = TimeDelta::FromMinutes(1);
+
+const WeeklyTimeIntervalVector kTestIntervals{
+    // Monday 10:15 AM to Monday 3:30 PM.
+    WeeklyTimeInterval(WeeklyTime(1, kHour * 10 + kMinute * 15),
+                       WeeklyTime(1, kHour * 15 + kMinute * 30)),
+    // Wednesday 8:30 PM to Thursday 8:40 AM.
+    WeeklyTimeInterval(WeeklyTime(3, kHour * 20 + kMinute * 30),
+                       WeeklyTime(4, kHour * 8 + kMinute * 40)),
+};
+
+class UmUpdateTimeRestrictionsPolicyImplTest : public UmPolicyTestBase {
+ protected:
+  UmUpdateTimeRestrictionsPolicyImplTest() {
+    policy_ = std::make_unique<UpdateTimeRestrictionsPolicyImpl>();
+  }
+
+  void TestPolicy(const Time::Exploded& exploded,
+                  const WeeklyTimeIntervalVector& test_intervals,
+                  const EvalStatus& expected_value,
+                  bool kiosk) {
+    if (kiosk)
+      fake_state_.device_policy_provider()
+          ->var_auto_launched_kiosk_app_id()
+          ->reset(new string("myapp"));
+
+    Time time;
+    EXPECT_TRUE(Time::FromLocalExploded(exploded, &time));
+    fake_clock_.SetWallclockTime(time);
+    SetUpDefaultTimeProvider();
+    fake_state_.device_policy_provider()
+        ->var_disallowed_time_intervals()
+        ->reset(new WeeklyTimeIntervalVector(test_intervals));
+    ErrorCode result;
+    InstallPlan install_plan;
+    ExpectPolicyStatus(
+        expected_value, &Policy::UpdateCanBeApplied, &result, &install_plan);
+    if (expected_value == EvalStatus::kSucceeded)
+      EXPECT_EQ(result, ErrorCode::kOmahaUpdateDeferredPerPolicy);
+  }
+};
+
+// If there are no intervals, then the check should always return kContinue.
+TEST_F(UmUpdateTimeRestrictionsPolicyImplTest, NoIntervalsSetTest) {
+  Time::Exploded random_time{2018, 7, 1, 9, 12, 30, 0, 0};
+  TestPolicy(random_time,
+             WeeklyTimeIntervalVector(),
+             EvalStatus::kContinue,
+             /* kiosk = */ true);
+}
+
+// Check that all intervals are checked.
+TEST_F(UmUpdateTimeRestrictionsPolicyImplTest, TimeInRange) {
+  // Monday, July 9th 2018 12:30 PM.
+  Time::Exploded first_interval_time{2018, 7, 1, 9, 12, 30, 0, 0};
+  TestPolicy(first_interval_time,
+             kTestIntervals,
+             EvalStatus::kSucceeded,
+             /* kiosk = */ true);
+
+  // Check second interval.
+  // Thursday, July 12th 2018 4:30 AM.
+  Time::Exploded second_interval_time{2018, 7, 4, 12, 4, 30, 0, 0};
+  TestPolicy(second_interval_time,
+             kTestIntervals,
+             EvalStatus::kSucceeded,
+             /* kiosk = */ true);
+}
+
+TEST_F(UmUpdateTimeRestrictionsPolicyImplTest, TimeOutOfRange) {
+  // Monday, July 9th 2018 6:30 PM.
+  Time::Exploded out_of_range_time{2018, 7, 1, 9, 18, 30, 0, 0};
+  TestPolicy(out_of_range_time,
+             kTestIntervals,
+             EvalStatus::kContinue,
+             /* kiosk = */ true);
+}
+
+TEST_F(UmUpdateTimeRestrictionsPolicyImplTest, NoKioskDisablesPolicy) {
+  Time::Exploded in_range_time{2018, 7, 1, 9, 12, 30, 0, 0};
+  TestPolicy(in_range_time,
+             kTestIntervals,
+             EvalStatus::kContinue,
+             /* kiosk = */ false);
+}
+}  // namespace chromeos_update_manager
diff --git a/update_manager/weekly_time.cc b/update_manager/weekly_time.cc
new file mode 100644
index 0000000..e478f9f
--- /dev/null
+++ b/update_manager/weekly_time.cc
@@ -0,0 +1,75 @@
+//
+// Copyright (C) 2018 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/weekly_time.h"
+
+#include <base/strings/string_number_conversions.h>
+#include <base/strings/stringprintf.h>
+#include <base/time/time.h>
+
+using base::Time;
+using base::TimeDelta;
+using std::string;
+
+namespace {
+const int kDaysInWeek = 7;
+}
+
+namespace chromeos_update_manager {
+
+TimeDelta WeeklyTime::GetDurationTo(const WeeklyTime& other) const {
+  if (other.TimeFromStartOfWeek() < TimeFromStartOfWeek()) {
+    return other.TimeFromStartOfWeek() +
+           (TimeDelta::FromDays(kDaysInWeek) - TimeFromStartOfWeek());
+  }
+  return other.TimeFromStartOfWeek() - TimeFromStartOfWeek();
+}
+
+TimeDelta WeeklyTime::TimeFromStartOfWeek() const {
+  return TimeDelta::FromDays(day_of_week_) + time_;
+}
+
+void WeeklyTime::AddTime(const TimeDelta& offset) {
+  time_ += offset;
+  int days_over = time_.InDays();
+  time_ -= TimeDelta::FromDays(days_over);
+  day_of_week_ = (day_of_week_ + days_over - 1) % kDaysInWeek + 1;
+}
+
+// static
+WeeklyTime WeeklyTime::FromTime(const Time& time) {
+  Time::Exploded exploded;
+  time.LocalExplode(&exploded);
+  return WeeklyTime(exploded.day_of_week,
+                    TimeDelta::FromHours(exploded.hour) +
+                        TimeDelta::FromMinutes(exploded.minute));
+}
+
+bool WeeklyTimeInterval::InRange(const WeeklyTime& time) const {
+  return time == start_ ||
+         (time.GetDurationTo(start_) >= time.GetDurationTo(end_) &&
+          time != end_);
+}
+
+string WeeklyTimeInterval::ToString() const {
+  return base::StringPrintf(
+      "Start: day_of_week=%d time=%d\nEnd: day_of_week=%d time=%d",
+      start_.day_of_week(),
+      start_.time().InMinutes(),
+      end_.day_of_week(),
+      end_.time().InMinutes());
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/weekly_time.h b/update_manager/weekly_time.h
new file mode 100644
index 0000000..9e3a039
--- /dev/null
+++ b/update_manager/weekly_time.h
@@ -0,0 +1,97 @@
+//
+// Copyright (C) 2018 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_WEEKLY_TIME_H_
+#define UPDATE_ENGINE_UPDATE_MANAGER_WEEKLY_TIME_H_
+
+#include <string>
+#include <vector>
+
+#include <base/time/time.h>
+
+namespace chromeos_update_manager {
+
+// Represents a day of the week and the time since it started.
+class WeeklyTime {
+ public:
+  // Day of week (Sunday = 0 and so on) and time since start of the day (12 AM).
+  WeeklyTime(const int& day_of_week, const base::TimeDelta& time)
+      : day_of_week_(day_of_week), time_(time) {}
+
+  // Create a weekly time from a time object.
+  static WeeklyTime FromTime(const base::Time& time);
+
+  bool operator==(const WeeklyTime& other) const {
+    return time_ == other.time() && day_of_week_ == other.day_of_week();
+  }
+
+  bool operator!=(const WeeklyTime& other) const { return !(*this == other); }
+
+  // Return the duration between WeeklyTime and |other|. |other| is always
+  // considered to be after WeeklyTime. i.e. calling this function on [Friday
+  // 12:00, Monday 12:00] would return 3 days.
+  base::TimeDelta GetDurationTo(const WeeklyTime& other) const;
+
+  // Gets the weekly time represented as a time delta.
+  base::TimeDelta TimeFromStartOfWeek() const;
+
+  // Adds the given |offset| to the time with proper wraparound (e.g. Sunday + 1
+  // day = Monday).
+  void AddTime(const base::TimeDelta& offset);
+
+  int day_of_week() const { return day_of_week_; }
+
+  base::TimeDelta time() const { return time_; }
+
+ private:
+  int day_of_week_;
+  base::TimeDelta time_;
+};
+
+// Represents an interval of time during a week represented with WeeklyTime
+// objects. This interval can span at most 7 days. |end| is always considered to
+// be after |start|, this is possible since the times of the week are cyclic.
+// For example, the interval [Thursday 12:00, Monday 12:00) will span the time
+// between Thursday and Monday.
+class WeeklyTimeInterval {
+ public:
+  WeeklyTimeInterval(const WeeklyTime& start, const WeeklyTime& end)
+      : start_(start), end_(end) {}
+
+  // Determines if |time| is in this interval.
+  bool InRange(const WeeklyTime& time) const;
+
+  WeeklyTime start() const { return start_; }
+
+  WeeklyTime end() const { return end_; }
+
+  bool operator==(const WeeklyTimeInterval& other) const {
+    return start_ == other.start() && end_ == other.end();
+  }
+
+  // Converts the interval to a string. Used for the BoxedValue ToString
+  // function.
+  std::string ToString() const;
+
+ private:
+  WeeklyTime start_;
+  WeeklyTime end_;
+};
+
+using WeeklyTimeIntervalVector = std::vector<WeeklyTimeInterval>;
+
+}  // namespace chromeos_update_manager
+
+#endif  // UPDATE_ENGINE_UPDATE_MANAGER_WEEKLY_TIME_H_
diff --git a/update_manager/weekly_time_unittest.cc b/update_manager/weekly_time_unittest.cc
new file mode 100644
index 0000000..52c5425
--- /dev/null
+++ b/update_manager/weekly_time_unittest.cc
@@ -0,0 +1,212 @@
+//
+// Copyright (C) 2018 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/weekly_time.h"
+
+#include <tuple>
+
+#include <base/time/time.h>
+#include <gtest/gtest.h>
+
+using base::TimeDelta;
+using std::tuple;
+
+namespace chromeos_update_manager {
+
+namespace {
+
+enum {
+  kSunday = 0,
+  kMonday,
+  kTuesday,
+  kWednesday,
+  kThursday,
+  kFriday,
+  kSaturday
+};
+
+}  // namespace
+
+class WeeklyTimeDurationTest
+    : public testing::TestWithParam<tuple<int /* start_day_of_week */,
+                                          TimeDelta /* start_time */,
+                                          int /* end_day_of_week */,
+                                          TimeDelta /* end_time */,
+                                          TimeDelta /* expected result */>> {
+ protected:
+  int start_day_of_week() { return std::get<0>(GetParam()); }
+  TimeDelta start_time() { return std::get<1>(GetParam()); }
+  int end_day_of_week() { return std::get<2>(GetParam()); }
+  TimeDelta end_time() { return std::get<3>(GetParam()); }
+  TimeDelta result() { return std::get<4>(GetParam()); }
+};
+
+TEST_P(WeeklyTimeDurationTest, GetDurationTo) {
+  WeeklyTime start = WeeklyTime(start_day_of_week(), start_time());
+  WeeklyTime end = WeeklyTime(end_day_of_week(), end_time());
+
+  EXPECT_EQ(result(), start.GetDurationTo(end));
+}
+
+INSTANTIATE_TEST_CASE_P(
+    SameMinutes,
+    WeeklyTimeDurationTest,
+    testing::Values(std::make_tuple(kThursday,
+                                    TimeDelta::FromMinutes(30),
+                                    kSaturday,
+                                    TimeDelta::FromMinutes(30),
+                                    TimeDelta::FromDays(2))));
+
+INSTANTIATE_TEST_CASE_P(
+    DifferentMinutes,
+    WeeklyTimeDurationTest,
+    testing::Values(std::make_tuple(kMonday,
+                                    TimeDelta::FromMinutes(10),
+                                    kWednesday,
+                                    TimeDelta::FromMinutes(30),
+                                    TimeDelta::FromDays(2) +
+                                        TimeDelta::FromMinutes(20))));
+
+INSTANTIATE_TEST_CASE_P(
+    EndLessThanStartSameMinutes,
+    WeeklyTimeDurationTest,
+    testing::Values(std::make_tuple(kSaturday,
+                                    TimeDelta::FromMinutes(100),
+                                    kTuesday,
+                                    TimeDelta::FromMinutes(100),
+                                    TimeDelta::FromDays(3))));
+
+INSTANTIATE_TEST_CASE_P(
+    EndLessThanStartDifferentMinutes,
+    WeeklyTimeDurationTest,
+    testing::Values(std::make_tuple(kSaturday,
+                                    TimeDelta::FromMinutes(150),
+                                    kMonday,
+                                    TimeDelta::FromMinutes(10),
+                                    TimeDelta::FromDays(2) -
+                                        TimeDelta::FromMinutes(140))));
+
+class WeeklyTimeOffsetTest
+    : public testing::TestWithParam<tuple<int /* day_of_week */,
+                                          TimeDelta /* time */,
+                                          TimeDelta /* offset */,
+                                          WeeklyTime /* expected result */>> {
+ protected:
+  int day_of_week() { return std::get<0>(GetParam()); }
+  TimeDelta time() { return std::get<1>(GetParam()); }
+  TimeDelta offset() { return std::get<2>(GetParam()); }
+  WeeklyTime result() { return std::get<3>(GetParam()); }
+};
+
+TEST_P(WeeklyTimeOffsetTest, WeekTimeAddTime) {
+  WeeklyTime test_time = WeeklyTime(day_of_week(), time());
+  test_time.AddTime(offset());
+
+  EXPECT_EQ(result(), test_time);
+}
+
+INSTANTIATE_TEST_CASE_P(
+    SameDayTest,
+    WeeklyTimeOffsetTest,
+    testing::Values(std::make_tuple(kTuesday,
+                                    TimeDelta::FromMinutes(200),
+                                    TimeDelta::FromMinutes(400),
+                                    WeeklyTime(kTuesday,
+                                               TimeDelta::FromMinutes(600)))));
+
+INSTANTIATE_TEST_CASE_P(DayChangeTest,
+                        WeeklyTimeOffsetTest,
+                        testing::Values(std::make_tuple(
+                            kThursday,
+                            TimeDelta::FromHours(23),
+                            TimeDelta::FromHours(2),
+                            WeeklyTime(kFriday, TimeDelta::FromHours(1)))));
+
+INSTANTIATE_TEST_CASE_P(DayChangeTestOver7,
+                        WeeklyTimeOffsetTest,
+                        testing::Values(std::make_tuple(
+                            kSunday,
+                            TimeDelta::FromHours(20),
+                            TimeDelta::FromDays(3),
+                            WeeklyTime(kWednesday, TimeDelta::FromHours(20)))));
+
+class WeeklyTimeIntervalRangeTest
+    : public testing::TestWithParam<tuple<int /* test_day_of_week */,
+                                          int /* test_time */,
+                                          bool /* in regular interval */,
+                                          bool /* in short interval */,
+                                          bool /* |start| < | */>> {
+ protected:
+  int day_of_week() { return std::get<0>(GetParam()); }
+  int minutes() { return std::get<1>(GetParam()); }
+  bool regular_result() { return std::get<2>(GetParam()); }
+  bool short_result() { return std::get<3>(GetParam()); }
+  bool wraparound_result() { return std::get<4>(GetParam()); }
+};
+
+TEST_P(WeeklyTimeIntervalRangeTest, InRange) {
+  WeeklyTime test =
+      WeeklyTime(day_of_week(), TimeDelta::FromMinutes(minutes()));
+  WeeklyTimeInterval interval_regular =
+      WeeklyTimeInterval(WeeklyTime(kMonday, TimeDelta::FromMinutes(10)),
+                         WeeklyTime(kWednesday, TimeDelta::FromMinutes(30)));
+  WeeklyTimeInterval interval_short =
+      WeeklyTimeInterval(WeeklyTime(kThursday, TimeDelta::FromMinutes(10)),
+                         WeeklyTime(kThursday, TimeDelta::FromMinutes(11)));
+
+  WeeklyTimeInterval interval_wraparound =
+      WeeklyTimeInterval(WeeklyTime(kFriday, TimeDelta::FromMinutes(10)),
+                         WeeklyTime(kTuesday, TimeDelta::FromMinutes(30)));
+
+  EXPECT_EQ(regular_result(), interval_regular.InRange(test));
+  EXPECT_EQ(short_result(), interval_short.InRange(test));
+  EXPECT_EQ(wraparound_result(), interval_wraparound.InRange(test));
+}
+
+// Test the left side of the range being inclusive.
+INSTANTIATE_TEST_CASE_P(
+    InclusiveSuccessLeft,
+    WeeklyTimeIntervalRangeTest,
+    testing::Values(std::make_tuple(kThursday, 10, false, true, false)));
+
+// Test the right side of the range being exclusive.
+INSTANTIATE_TEST_CASE_P(
+    ExclusiveSuccessRight,
+    WeeklyTimeIntervalRangeTest,
+    testing::Values(std::make_tuple(kThursday, 11, false, false, false)));
+
+// Test falling out of the interval by a small amount.
+INSTANTIATE_TEST_CASE_P(
+    FailOutsideRangeSmall,
+    WeeklyTimeIntervalRangeTest,
+    testing::Values(std::make_tuple(kThursday, 12, false, false, false)));
+
+// These test cases check that intervals wrap around properly.
+INSTANTIATE_TEST_CASE_P(
+    WraparoundOutside,
+    WeeklyTimeIntervalRangeTest,
+    testing::Values(std::make_tuple(kWednesday, 10, true, false, false)));
+
+INSTANTIATE_TEST_CASE_P(
+    WraparoundInsideRight,
+    WeeklyTimeIntervalRangeTest,
+    testing::Values(std::make_tuple(kSaturday, 10, false, false, true)));
+
+INSTANTIATE_TEST_CASE_P(
+    WraparoundInsideLeft,
+    WeeklyTimeIntervalRangeTest,
+    testing::Values(std::make_tuple(kMonday, 0, false, false, true)));
+
+}  // namespace chromeos_update_manager
diff --git a/update_status_utils.cc b/update_status_utils.cc
index ff039b8..5de3381 100644
--- a/update_status_utils.cc
+++ b/update_status_utils.cc
@@ -30,6 +30,8 @@
       return update_engine::kUpdateStatusCheckingForUpdate;
     case UpdateStatus::UPDATE_AVAILABLE:
       return update_engine::kUpdateStatusUpdateAvailable;
+    case UpdateStatus::NEED_PERMISSION_TO_UPDATE:
+      return update_engine::kUpdateStatusNeedPermissionToUpdate;
     case UpdateStatus::DOWNLOADING:
       return update_engine::kUpdateStatusDownloading;
     case UpdateStatus::VERIFYING:
@@ -61,6 +63,9 @@
   } else if (s == update_engine::kUpdateStatusUpdateAvailable) {
     *status = UpdateStatus::UPDATE_AVAILABLE;
     return true;
+  } else if (s == update_engine::kUpdateStatusNeedPermissionToUpdate) {
+    *status = UpdateStatus::NEED_PERMISSION_TO_UPDATE;
+    return true;
   } else if (s == update_engine::kUpdateStatusDownloading) {
     *status = UpdateStatus::DOWNLOADING;
     return true;