Merge "Merge remote-tracking branch 'remotes/aosp/upstream-master' into merge-cros"
diff --git a/BUILD.gn b/BUILD.gn
index 9575fab..aec447c 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -44,6 +44,7 @@
       ":test_subprocess",
       ":update_engine-test_images",
       ":update_engine-testkeys",
+      ":update_engine-testkeys-ec",
       ":update_engine_test_libs",
       ":update_engine_unittests",
     ]
@@ -60,6 +61,7 @@
 pkg_config("target_defaults") {
   cflags_cc = [
     "-fno-strict-aliasing",
+    "-std=gnu++17",
     "-Wnon-virtual-dtor",
   ]
   cflags = [
@@ -75,6 +77,7 @@
     "__CHROMEOS__",
     "_FILE_OFFSET_BITS=64",
     "_POSIX_C_SOURCE=199309L",
+    "USE_CFM=${use.cfm}",
     "USE_DBUS=${use.dbus}",
     "USE_FEC=0",
     "USE_HWID_OVERRIDE=${use.hwid_override}",
@@ -92,7 +95,7 @@
   # NOSORT
   pkg_deps = [
     "libbrillo",
-    "libchrome-${libbase_ver}",
+    "libchrome",
 
     # system_api depends on protobuf (or protobuf-lite). It must appear
     # before protobuf here or the linker flags won't be in the right
@@ -217,6 +220,7 @@
     "payload_state.cc",
     "power_manager_chromeos.cc",
     "real_system_state.cc",
+    "requisition_util.cc",
     "shill_proxy.cc",
     "update_attempter.cc",
     "update_boot_flags_action.cc",
@@ -258,7 +262,7 @@
     "expat",
     "libcurl",
     "libdebugd-client",
-    "libmetrics-${libbase_ver}",
+    "libmetrics",
     "libpower_manager-client",
     "libsession_manager-client",
     "libshill-client",
@@ -418,10 +422,20 @@
     openssl_pem_out_dir = "include/update_engine"
     sources = [
       "unittest_key.pem",
+      "unittest_key_RSA4096.pem",
       "unittest_key2.pem",
     ]
   }
 
+  genopenssl_key("update_engine-testkeys-ec") {
+    openssl_pem_in_dir = "."
+    openssl_pem_out_dir = "include/update_engine"
+    openssl_pem_algorithm = "ec"
+    sources = [
+      "unittest_key_EC.pem",
+    ]
+  }
+
   # Unpacks sample images used for testing.
   tar_bunzip2("update_engine-test_images") {
     image_out_dir = "."
@@ -515,11 +529,13 @@
       "payload_generator/squashfs_filesystem_unittest.cc",
       "payload_generator/zip_unittest.cc",
       "payload_state_unittest.cc",
+      "requisition_util_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/enterprise_device_policy_impl_unittest.cc",
       "update_manager/evaluation_context_unittest.cc",
       "update_manager/generic_variables_unittest.cc",
       "update_manager/prng_unittest.cc",
@@ -548,7 +564,7 @@
     ]
     pkg_deps = [
       "libbrillo-test",
-      "libchrome-test-${libbase_ver}",
+      "libchrome-test",
       "libdebugd-client-test",
       "libpower_manager-client-test",
       "libsession_manager-client-test",
@@ -572,7 +588,7 @@
     ]
     pkg_deps = [
       "libbrillo-test",
-      "libchrome-test-${libbase_ver}",
+      "libchrome-test",
     ]
     deps = [
       ":libupdate_engine",
@@ -587,7 +603,7 @@
     ]
     pkg_deps = [
       "libbrillo-test",
-      "libchrome-test-${libbase_ver}",
+      "libchrome-test",
     ]
     deps = [
       ":libupdate_engine",
diff --git a/boot_control_chromeos.cc b/boot_control_chromeos.cc
index 95456f0..da2c891 100644
--- a/boot_control_chromeos.cc
+++ b/boot_control_chromeos.cc
@@ -128,8 +128,9 @@
   }
   if (current_slot_ >= num_slots_) {
     LOG(ERROR) << "Couldn't find the slot number corresponding to the "
-               << "partition " << boot_device << ", number of slots: "
-               << num_slots_ << ". This device is not updateable.";
+               << "partition " << boot_device
+               << ", number of slots: " << num_slots_
+               << ". This device is not updateable.";
     num_slots_ = 1;
     current_slot_ = BootControlInterface::kInvalidSlot;
     return false;
diff --git a/client_library/client_dbus.cc b/client_library/client_dbus.cc
index 8e9a7fd..30ad78c 100644
--- a/client_library/client_dbus.cc
+++ b/client_library/client_dbus.cc
@@ -16,7 +16,7 @@
 
 #include "update_engine/client_library/client_dbus.h"
 
-#include <base/message_loop/message_loop.h>
+#include <base/message_loop/message_loop_current.h>
 
 #include <memory>
 
diff --git a/common/constants.cc b/common/constants.cc
index c85ba54..8883668 100644
--- a/common/constants.cc
+++ b/common/constants.cc
@@ -64,6 +64,8 @@
 const char kPrefsP2PFirstAttemptTimestamp[] = "p2p-first-attempt-timestamp";
 const char kPrefsP2PNumAttempts[] = "p2p-num-attempts";
 const char kPrefsPayloadAttemptNumber[] = "payload-attempt-number";
+const char kPrefsTestUpdateCheckIntervalTimeout[] =
+    "test-update-check-interval-timeout";
 // Keep |kPrefsPingActive| in sync with |kDlcMetadataFilePingActive| in
 // dlcservice.
 const char kPrefsPingActive[] = "active";
diff --git a/common/constants.h b/common/constants.h
index 7170201..3685102 100644
--- a/common/constants.h
+++ b/common/constants.h
@@ -67,6 +67,7 @@
 extern const char kPrefsP2PFirstAttemptTimestamp[];
 extern const char kPrefsP2PNumAttempts[];
 extern const char kPrefsPayloadAttemptNumber[];
+extern const char kPrefsTestUpdateCheckIntervalTimeout[];
 extern const char kPrefsPingActive[];
 extern const char kPrefsPingLastActive[];
 extern const char kPrefsPingLastRollcall[];
diff --git a/common/error_code.h b/common/error_code.h
index e473a05..7d9cfff 100644
--- a/common/error_code.h
+++ b/common/error_code.h
@@ -85,6 +85,7 @@
   kUnresolvedHostRecovered = 59,
   kNotEnoughSpace = 60,
   kDeviceCorrupted = 61,
+  kPackageExcludedFromUpdate = 62,
 
   // VERY IMPORTANT! When adding new error codes:
   //
diff --git a/common/error_code_utils.cc b/common/error_code_utils.cc
index 64df24a..cda4c7e 100644
--- a/common/error_code_utils.cc
+++ b/common/error_code_utils.cc
@@ -171,6 +171,8 @@
       return "ErrorCode::kNotEnoughSpace";
     case ErrorCode::kDeviceCorrupted:
       return "ErrorCode::kDeviceCorrupted";
+    case ErrorCode::kPackageExcludedFromUpdate:
+      return "ErrorCode::kPackageExcludedFromUpdate";
       // 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_boot_control.h b/common/fake_boot_control.h
index 5d8823a..98b93e6 100644
--- a/common/fake_boot_control.h
+++ b/common/fake_boot_control.h
@@ -116,7 +116,7 @@
     is_bootable_[slot] = bootable;
   }
 
-  DynamicPartitionControlInterface* GetDynamicPartitionControl() {
+  DynamicPartitionControlInterface* GetDynamicPartitionControl() override {
     return dynamic_partition_control_.get();
   }
 
diff --git a/common/fake_hardware.h b/common/fake_hardware.h
index 82382ff..00a212e 100644
--- a/common/fake_hardware.h
+++ b/common/fake_hardware.h
@@ -76,10 +76,6 @@
 
   std::string GetHardwareClass() const override { return hardware_class_; }
 
-  std::string GetFirmwareVersion() const override { return firmware_version_; }
-
-  std::string GetECVersion() const override { return ec_version_; }
-
   std::string GetDeviceRequisition() const override {
     return device_requisition_;
   }
@@ -176,12 +172,6 @@
     hardware_class_ = hardware_class;
   }
 
-  void SetFirmwareVersion(const std::string& firmware_version) {
-    firmware_version_ = firmware_version;
-  }
-
-  void SetECVersion(const std::string& ec_version) { ec_version_ = ec_version; }
-
   void SetDeviceRequisition(const std::string& requisition) {
     device_requisition_ = requisition;
   }
@@ -233,8 +223,6 @@
   // 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"};
   std::string device_requisition_{"fake_requisition"};
   int min_kernel_key_version_{kMinKernelKeyVersion};
   int min_firmware_key_version_{kMinFirmwareKeyVersion};
diff --git a/common/hardware_interface.h b/common/hardware_interface.h
index b37b007..cad32fc 100644
--- a/common/hardware_interface.h
+++ b/common/hardware_interface.h
@@ -64,14 +64,6 @@
   // Returns the HWID or an empty string on error.
   virtual std::string GetHardwareClass() const = 0;
 
-  // Returns the firmware version or an empty string if the system is
-  // not running chrome os firmware.
-  virtual std::string GetFirmwareVersion() const = 0;
-
-  // Returns the ec version or an empty string if the system is not
-  // running a custom chrome os ec.
-  virtual std::string GetECVersion() const = 0;
-
   // Returns the OEM device requisition or an empty string if the system does
   // not have a requisition, or if not running Chrome OS.
   virtual std::string GetDeviceRequisition() const = 0;
diff --git a/common/hash_calculator_unittest.cc b/common/hash_calculator_unittest.cc
index e8f73d5..fe7d543 100644
--- a/common/hash_calculator_unittest.cc
+++ b/common/hash_calculator_unittest.cc
@@ -104,7 +104,7 @@
 }
 
 TEST_F(HashCalculatorTest, UpdateFileSimpleTest) {
-  test_utils::ScopedTempFile data_file("data.XXXXXX");
+  ScopedTempFile data_file("data.XXXXXX");
   ASSERT_TRUE(test_utils::WriteFileString(data_file.path(), "hi"));
 
   for (const int length : {-1, 2, 10}) {
@@ -126,7 +126,7 @@
 }
 
 TEST_F(HashCalculatorTest, RawHashOfFileSimpleTest) {
-  test_utils::ScopedTempFile data_file("data.XXXXXX");
+  ScopedTempFile data_file("data.XXXXXX");
   ASSERT_TRUE(test_utils::WriteFileString(data_file.path(), "hi"));
 
   for (const int length : {-1, 2, 10}) {
diff --git a/common/http_fetcher_unittest.cc b/common/http_fetcher_unittest.cc
index 9338087..99ea99b 100644
--- a/common/http_fetcher_unittest.cc
+++ b/common/http_fetcher_unittest.cc
@@ -28,11 +28,16 @@
 #include <base/bind.h>
 #include <base/location.h>
 #include <base/logging.h>
+#if BASE_VER < 780000  // Android
 #include <base/message_loop/message_loop.h>
+#endif  // BASE_VER < 780000
 #include <base/stl_util.h>
 #include <base/strings/string_number_conversions.h>
 #include <base/strings/string_util.h>
 #include <base/strings/stringprintf.h>
+#if BASE_VER >= 780000  // CrOS
+#include <base/task/single_thread_task_executor.h>
+#endif  // BASE_VER >= 780000
 #include <base/time/time.h>
 #include <brillo/message_loops/base_message_loop.h>
 #include <brillo/message_loops/message_loop.h>
@@ -364,7 +369,7 @@
   HttpServer* CreateServer() override { return new NullHttpServer; }
 
  private:
-  test_utils::ScopedTempFile temp_file_{"ue_file_fetcher.XXXXXX"};
+  ScopedTempFile temp_file_{"ue_file_fetcher.XXXXXX"};
 };
 
 class MultiRangeHttpFetcherOverFileFetcherTest : public FileFetcherTest {
@@ -403,8 +408,13 @@
 template <typename T>
 class HttpFetcherTest : public ::testing::Test {
  public:
+#if BASE_VER < 780000  // Android
   base::MessageLoopForIO base_loop_;
   brillo::BaseMessageLoop loop_{&base_loop_};
+#else   // Chrome OS
+  base::SingleThreadTaskExecutor base_loop_{base::MessagePumpType::IO};
+  brillo::BaseMessageLoop loop_{base_loop_.task_runner()};
+#endif  // BASE_VER < 780000
 
   T test_;
 
diff --git a/common/mock_hardware.h b/common/mock_hardware.h
index 84c0c5b..071906b 100644
--- a/common/mock_hardware.h
+++ b/common/mock_hardware.h
@@ -45,11 +45,6 @@
     ON_CALL(*this, GetHardwareClass())
         .WillByDefault(
             testing::Invoke(&fake_, &FakeHardware::GetHardwareClass));
-    ON_CALL(*this, GetFirmwareVersion())
-        .WillByDefault(
-            testing::Invoke(&fake_, &FakeHardware::GetFirmwareVersion));
-    ON_CALL(*this, GetECVersion())
-        .WillByDefault(testing::Invoke(&fake_, &FakeHardware::GetECVersion));
     ON_CALL(*this, GetMinKernelKeyVersion())
         .WillByDefault(
             testing::Invoke(&fake_, &FakeHardware::GetMinKernelKeyVersion));
@@ -90,8 +85,6 @@
   MOCK_CONST_METHOD0(IsOOBEEnabled, bool());
   MOCK_CONST_METHOD1(IsOOBEComplete, bool(base::Time* out_time_of_oobe));
   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());
diff --git a/common/subprocess.h b/common/subprocess.h
index 179a5c5..2ed8b81 100644
--- a/common/subprocess.h
+++ b/common/subprocess.h
@@ -37,7 +37,7 @@
 #include <brillo/process.h>
 #include <brillo/process_reaper.h>
 #endif  // __CHROMEOS__
-#include <gtest/gtest_prod.h>  // for FRIEND_TEST
+#include <gtest/gtest_prod.h>
 
 // The Subprocess class is a singleton. It's used to spawn off a subprocess
 // and get notified when the subprocess exits. The result of Exec() can
diff --git a/common/subprocess_unittest.cc b/common/subprocess_unittest.cc
index b4d068f..ff4158e 100644
--- a/common/subprocess_unittest.cc
+++ b/common/subprocess_unittest.cc
@@ -28,9 +28,14 @@
 #include <base/bind.h>
 #include <base/files/scoped_temp_dir.h>
 #include <base/location.h>
+#if BASE_VER < 780000  // Android
 #include <base/message_loop/message_loop.h>
+#endif  // BASE_VER < 780000
 #include <base/strings/string_util.h>
 #include <base/strings/stringprintf.h>
+#if BASE_VER >= 780000  // Chrome OS
+#include <base/task/single_thread_task_executor.h>
+#endif  // BASE_VER >= 780000
 #include <base/time/time.h>
 #include <brillo/message_loops/base_message_loop.h>
 #include <brillo/message_loops/message_loop.h>
@@ -70,8 +75,13 @@
     subprocess_.Init(&async_signal_handler_);
   }
 
+#if BASE_VER < 780000  // Android
   base::MessageLoopForIO base_loop_;
   brillo::BaseMessageLoop loop_{&base_loop_};
+#else   // Chrome OS
+  base::SingleThreadTaskExecutor base_loop_{base::MessagePumpType::IO};
+  brillo::BaseMessageLoop loop_{base_loop_.task_runner()};
+#endif  // BASE_VER < 780000
   brillo::AsynchronousSignalHandler async_signal_handler_;
   Subprocess subprocess_;
   unique_ptr<base::FileDescriptorWatcher::Controller> watcher_;
diff --git a/common/test_utils.h b/common/test_utils.h
index 63ea749..bb5a678 100644
--- a/common/test_utils.h
+++ b/common/test_utils.h
@@ -138,22 +138,6 @@
   DISALLOW_COPY_AND_ASSIGN(ScopedLoopbackDeviceBinder);
 };
 
-class ScopedTempFile {
- public:
-  ScopedTempFile() : ScopedTempFile("update_engine_test_temp_file.XXXXXX") {}
-
-  explicit ScopedTempFile(const std::string& pattern) {
-    EXPECT_TRUE(utils::MakeTempFile(pattern, &path_, nullptr));
-    unlinker_.reset(new ScopedPathUnlinker(path_));
-  }
-
-  const std::string& path() const { return path_; }
-
- private:
-  std::string path_;
-  std::unique_ptr<ScopedPathUnlinker> unlinker_;
-};
-
 class ScopedLoopMounter {
  public:
   explicit ScopedLoopMounter(const std::string& file_path,
diff --git a/common/utils.cc b/common/utils.cc
index 5d76f3f..c8924b1 100644
--- a/common/utils.cc
+++ b/common/utils.cc
@@ -112,27 +112,6 @@
 
 namespace utils {
 
-string ParseECVersion(string input_line) {
-  base::TrimWhitespaceASCII(input_line, base::TRIM_ALL, &input_line);
-
-  // At this point we want to convert the format key=value pair from mosys to
-  // a vector of key value pairs.
-  vector<pair<string, string>> kv_pairs;
-  if (base::SplitStringIntoKeyValuePairs(input_line, '=', ' ', &kv_pairs)) {
-    for (const pair<string, string>& kv_pair : kv_pairs) {
-      // Finally match against the fw_verion which may have quotes.
-      if (kv_pair.first == "fw_version") {
-        string output;
-        // Trim any quotes.
-        base::TrimString(kv_pair.second, "\"", &output);
-        return output;
-      }
-    }
-  }
-  LOG(ERROR) << "Unable to parse fwid from ec info.";
-  return "";
-}
-
 bool WriteFile(const char* path, const void* data, size_t data_len) {
   int fd = HANDLE_EINTR(open(path, O_WRONLY | O_CREAT | O_TRUNC, 0600));
   TEST_AND_RETURN_FALSE_ERRNO(fd >= 0);
@@ -911,6 +890,25 @@
   return true;
 }
 
+bool GetVpdValue(string key, string* result) {
+  int exit_code = 0;
+  string value, error;
+  vector<string> cmd = {"vpd_get_value", key};
+  if (!chromeos_update_engine::Subprocess::SynchronousExec(
+          cmd, &exit_code, &value, &error) ||
+      exit_code) {
+    LOG(ERROR) << "Failed to get vpd key for " << value
+               << " with exit code: " << exit_code << " and error: " << error;
+    return false;
+  } else if (!error.empty()) {
+    LOG(INFO) << "vpd_get_value succeeded but with following errors: " << error;
+  }
+
+  base::TrimWhitespaceASCII(value, base::TRIM_ALL, &value);
+  *result = value;
+  return true;
+}
+
 bool GetBootId(string* boot_id) {
   TEST_AND_RETURN_FALSE(
       base::ReadFileToString(base::FilePath(kBootIdPath), boot_id));
diff --git a/common/utils.h b/common/utils.h
index 0a1dc0c..05a92be 100644
--- a/common/utils.h
+++ b/common/utils.h
@@ -53,10 +53,6 @@
 std::string CalculateP2PFileId(const brillo::Blob& payload_hash,
                                size_t payload_size);
 
-// Parse the firmware version from one line of output from the
-// "mosys" command.
-std::string ParseECVersion(std::string input_line);
-
 // Writes the data passed to path. The file at path will be overwritten if it
 // exists. Returns true on success, false otherwise.
 bool WriteFile(const char* path, const void* data, size_t data_len);
@@ -292,6 +288,10 @@
 // reboot. Returns whether it succeeded getting the boot_id.
 bool GetBootId(std::string* boot_id);
 
+// Gets a string value from the vpd for a given key using the `vpd_get_value`
+// shell command. Returns true on success.
+bool GetVpdValue(std::string key, std::string* result);
+
 // This function gets the file path of the file pointed to by FileDiscriptor.
 std::string GetFilePath(int fd);
 
@@ -370,6 +370,42 @@
   DISALLOW_COPY_AND_ASSIGN(ScopedPathUnlinker);
 };
 
+class ScopedTempFile {
+ public:
+  ScopedTempFile() : ScopedTempFile("update_engine_temp.XXXXXX") {}
+
+  // If |open_fd| is true, a writable file descriptor will be opened for this
+  // file.
+  explicit ScopedTempFile(const std::string& pattern, bool open_fd = false) {
+    CHECK(utils::MakeTempFile(pattern, &path_, open_fd ? &fd_ : nullptr));
+    unlinker_.reset(new ScopedPathUnlinker(path_));
+    if (open_fd) {
+      CHECK_GE(fd_, 0);
+      fd_closer_.reset(new ScopedFdCloser(&fd_));
+    }
+  }
+  virtual ~ScopedTempFile() = default;
+
+  const std::string& path() const { return path_; }
+  int fd() const {
+    CHECK(fd_closer_);
+    return fd_;
+  }
+  void CloseFd() {
+    CHECK(fd_closer_);
+    fd_closer_.reset();
+  }
+
+ private:
+  std::string path_;
+  std::unique_ptr<ScopedPathUnlinker> unlinker_;
+
+  int fd_{-1};
+  std::unique_ptr<ScopedFdCloser> fd_closer_;
+
+  DISALLOW_COPY_AND_ASSIGN(ScopedTempFile);
+};
+
 // A little object to call ActionComplete on the ActionProcessor when
 // it's destructed.
 class ScopedActionCompleter {
diff --git a/common/utils_unittest.cc b/common/utils_unittest.cc
index d73b3da..20c6b84 100644
--- a/common/utils_unittest.cc
+++ b/common/utils_unittest.cc
@@ -41,25 +41,12 @@
 
 class UtilsTest : public ::testing::Test {};
 
-TEST(UtilsTest, CanParseECVersion) {
-  // Should be able to parse and valid key value line.
-  EXPECT_EQ("12345", utils::ParseECVersion("fw_version=12345"));
-  EXPECT_EQ("123456",
-            utils::ParseECVersion("b=1231a fw_version=123456 a=fasd2"));
-  EXPECT_EQ("12345", utils::ParseECVersion("fw_version=12345"));
-  EXPECT_EQ("00VFA616",
-            utils::ParseECVersion("vendor=\"sam\" fw_version=\"00VFA616\""));
-
-  // For invalid entries, should return the empty string.
-  EXPECT_EQ("", utils::ParseECVersion("b=1231a fw_version a=fasd2"));
-}
-
 TEST(UtilsTest, WriteFileOpenFailure) {
   EXPECT_FALSE(utils::WriteFile("/this/doesn't/exist", "hello", 5));
 }
 
 TEST(UtilsTest, WriteFileReadFile) {
-  test_utils::ScopedTempFile file;
+  ScopedTempFile file;
   EXPECT_TRUE(utils::WriteFile(file.path().c_str(), "hello", 5));
 
   brillo::Blob readback;
@@ -73,7 +60,7 @@
 }
 
 TEST(UtilsTest, ReadFileChunk) {
-  test_utils::ScopedTempFile file;
+  ScopedTempFile file;
   brillo::Blob data;
   const size_t kSize = 1024 * 1024;
   for (size_t i = 0; i < kSize; i++) {
@@ -162,7 +149,7 @@
 namespace {
 void GetFileFormatTester(const string& expected,
                          const vector<uint8_t>& contents) {
-  test_utils::ScopedTempFile file;
+  ScopedTempFile file;
   ASSERT_TRUE(utils::WriteFile(file.path().c_str(),
                                reinterpret_cast<const char*>(contents.data()),
                                contents.size()));
@@ -391,7 +378,7 @@
 }
 
 TEST(UtilsTest, RunAsRootUnmountFilesystemBusyFailureTest) {
-  test_utils::ScopedTempFile tmp_image("img.XXXXXX");
+  ScopedTempFile tmp_image("img.XXXXXX");
 
   EXPECT_TRUE(base::CopyFile(
       test_utils::GetBuildArtifactsPath().Append("gen/disk_ext2_4k.img"),
@@ -431,7 +418,7 @@
   EXPECT_TRUE(mnt_dir.CreateUniqueTempDir());
   EXPECT_FALSE(utils::IsMountpoint(mnt_dir.GetPath().value()));
 
-  test_utils::ScopedTempFile file;
+  ScopedTempFile file;
   EXPECT_FALSE(utils::IsMountpoint(file.path()));
 }
 
@@ -473,7 +460,7 @@
 }
 
 TEST(UtilsTest, GetFilePathTest) {
-  test_utils::ScopedTempFile file;
+  ScopedTempFile file;
   int fd = HANDLE_EINTR(open(file.path().c_str(), O_RDONLY));
   EXPECT_GE(fd, 0);
   EXPECT_EQ(file.path(), utils::GetFilePath(fd));
diff --git a/dynamic_partition_control_android_unittest.cc b/dynamic_partition_control_android_unittest.cc
index 223e177..c1e0daf 100644
--- a/dynamic_partition_control_android_unittest.cc
+++ b/dynamic_partition_control_android_unittest.cc
@@ -34,7 +34,6 @@
 using android::dm::DmDeviceState;
 using android::snapshot::MockSnapshotManager;
 using chromeos_update_engine::test_utils::ScopedLoopbackDeviceBinder;
-using chromeos_update_engine::test_utils::ScopedTempFile;
 using std::string;
 using testing::_;
 using testing::AnyNumber;
diff --git a/hardware_android.cc b/hardware_android.cc
index 5e24621..28c139a 100644
--- a/hardware_android.cc
+++ b/hardware_android.cc
@@ -45,8 +45,6 @@
 // Android properties that identify the hardware and potentially non-updatable
 // parts of the bootloader (such as the bootloader version and the baseband
 // version).
-const char kPropBootBootloader[] = "ro.boot.bootloader";
-const char kPropBootBaseband[] = "ro.boot.baseband";
 const char kPropProductManufacturer[] = "ro.product.manufacturer";
 const char kPropBootHardwareSKU[] = "ro.boot.hardware.sku";
 const char kPropBootRevision[] = "ro.boot.revision";
@@ -137,14 +135,6 @@
   return manufacturer + ":" + sku + ":" + revision;
 }
 
-string HardwareAndroid::GetFirmwareVersion() const {
-  return GetProperty(kPropBootBootloader, "");
-}
-
-string HardwareAndroid::GetECVersion() const {
-  return GetProperty(kPropBootBaseband, "");
-}
-
 string HardwareAndroid::GetDeviceRequisition() const {
   LOG(WARNING) << "STUB: Getting requisition is not supported.";
   return "";
diff --git a/hardware_android.h b/hardware_android.h
index d8fbbbe..4d10835 100644
--- a/hardware_android.h
+++ b/hardware_android.h
@@ -42,8 +42,6 @@
   bool IsOOBEEnabled() const override;
   bool IsOOBEComplete(base::Time* out_time_of_oobe) const override;
   std::string GetHardwareClass() const override;
-  std::string GetFirmwareVersion() const override;
-  std::string GetECVersion() const override;
   std::string GetDeviceRequisition() const override;
   int GetMinKernelKeyVersion() const override;
   int GetMinFirmwareKeyVersion() const override;
diff --git a/hardware_chromeos.cc b/hardware_chromeos.cc
index 807e086..dbb99db 100644
--- a/hardware_chromeos.cc
+++ b/hardware_chromeos.cc
@@ -38,6 +38,9 @@
 #include "update_engine/common/subprocess.h"
 #include "update_engine/common/utils.h"
 #include "update_engine/dbus_connection.h"
+#if USE_CFM
+#include "update_engine/requisition_util.h"
+#endif
 
 using std::string;
 using std::vector;
@@ -81,29 +84,6 @@
 
 const char* kActivePingKey = "first_active_omaha_ping_sent";
 
-const char* kOemRequisitionKey = "oem_device_requisition";
-
-// Gets a string value from the vpd for a given key using the `vpd_get_value`
-// shell command. Returns true on success.
-int GetVpdValue(string key, string* result) {
-  int exit_code = 0;
-  string value, error;
-  vector<string> cmd = {"vpd_get_value", key};
-  if (!chromeos_update_engine::Subprocess::SynchronousExec(
-          cmd, &exit_code, &value, &error) ||
-      exit_code) {
-    LOG(ERROR) << "Failed to get vpd key for " << value
-               << " with exit code: " << exit_code << " and error: " << error;
-    return false;
-  } else if (!error.empty()) {
-    LOG(INFO) << "vpd_get_value succeeded but with following errors: " << error;
-  }
-
-  base::TrimWhitespaceASCII(value, base::TRIM_ALL, &value);
-  *result = value;
-  return true;
-}
-
 }  // namespace
 
 namespace chromeos_update_engine {
@@ -195,28 +175,13 @@
   return ReadValueFromCrosSystem("hwid");
 }
 
-string HardwareChromeOS::GetFirmwareVersion() const {
-  return ReadValueFromCrosSystem("fwid");
-}
-
-string HardwareChromeOS::GetECVersion() const {
-  string input_line, error;
-  int exit_code = 0;
-  vector<string> cmd = {"/usr/sbin/mosys", "-k", "ec", "info"};
-
-  if (!Subprocess::SynchronousExec(cmd, &exit_code, &input_line, &error) ||
-      exit_code != 0) {
-    LOG(ERROR) << "Unable to read EC info from mosys with exit code: "
-               << exit_code << " and error: " << error;
-    return "";
-  }
-
-  return utils::ParseECVersion(input_line);
-}
-
 string HardwareChromeOS::GetDeviceRequisition() const {
-  string requisition;
-  return GetVpdValue(kOemRequisitionKey, &requisition) ? requisition : "";
+#if USE_CFM
+  const char* kLocalStatePath = "/home/chronos/Local State";
+  return ReadDeviceRequisition(base::FilePath(kLocalStatePath));
+#else
+  return "";
+#endif
 }
 
 int HardwareChromeOS::GetMinKernelKeyVersion() const {
@@ -341,7 +306,7 @@
 
 bool HardwareChromeOS::GetFirstActiveOmahaPingSent() const {
   string active_ping_str;
-  if (!GetVpdValue(kActivePingKey, &active_ping_str)) {
+  if (!utils::GetVpdValue(kActivePingKey, &active_ping_str)) {
     return false;
   }
 
diff --git a/hardware_chromeos.h b/hardware_chromeos.h
index bbfe273..9ee62f6 100644
--- a/hardware_chromeos.h
+++ b/hardware_chromeos.h
@@ -46,8 +46,6 @@
   bool IsOOBEEnabled() const override;
   bool IsOOBEComplete(base::Time* out_time_of_oobe) const override;
   std::string GetHardwareClass() const override;
-  std::string GetFirmwareVersion() const override;
-  std::string GetECVersion() const override;
   std::string GetDeviceRequisition() const override;
   int GetMinKernelKeyVersion() const override;
   int GetMinFirmwareKeyVersion() const override;
diff --git a/init/update-engine.conf b/init/update-engine.conf
index ca54c4a..36c89d7 100644
--- a/init/update-engine.conf
+++ b/init/update-engine.conf
@@ -37,7 +37,17 @@
 # Put update_engine process in its own cgroup.
 # Default cpu.shares is 1024.
 post-start script
-  cgroup_dir="/sys/fs/cgroup/cpu/${UPSTART_JOB}"
-  mkdir -p "${cgroup_dir}"
-  echo $(status | cut -f 4 -d ' ') > "${cgroup_dir}/tasks"
+  pid=$(status | cut -f 4 -d ' ')
+
+  cgroup_cpu_dir="/sys/fs/cgroup/cpu/${UPSTART_JOB}"
+  mkdir -p "${cgroup_cpu_dir}"
+  echo ${pid} > "${cgroup_cpu_dir}/tasks"
+
+  # Assigns net_cls handle 1:1 to packets generated from update_engine. For
+  # routing and tagging purposes, that value will be redefined in
+  # patchpanel/routing_service.h .
+  cgroup_net_cls_dir="/sys/fs/cgroup/net_cls/${UPSTART_JOB}"
+  mkdir -p "${cgroup_net_cls_dir}"
+  echo ${pid} > "${cgroup_net_cls_dir}/tasks"
+  echo "0x10001" > "${cgroup_net_cls_dir}/net_cls.classid"
 end script
diff --git a/libcurl_http_fetcher.cc b/libcurl_http_fetcher.cc
index bce0920..1599aac 100644
--- a/libcurl_http_fetcher.cc
+++ b/libcurl_http_fetcher.cc
@@ -458,10 +458,10 @@
     // There's either more work to do or we are paused, so we just keep the
     // file descriptors to watch up to date and exit, until we are done with the
     // work and we are not paused.
-#ifdef __ANDROID__
-    // When there's no base::SingleThreadTaskRunner on current thread, it's not
-    // possible to watch file descriptors. Just poll it later. This usually
-    // happens if brillo::FakeMessageLoop is used.
+    //
+    // When there's no |base::SingleThreadTaskRunner| on current thread, it's
+    // not possible to watch file descriptors. Just poll it later. This usually
+    // happens if |brillo::FakeMessageLoop| is used.
     if (!base::ThreadTaskRunnerHandle::IsSet()) {
       MessageLoop::current()->PostDelayedTask(
           FROM_HERE,
@@ -470,7 +470,6 @@
           TimeDelta::FromSeconds(1));
       return;
     }
-#endif
     SetupMessageLoopSources();
     return;
   }
diff --git a/libcurl_http_fetcher_unittest.cc b/libcurl_http_fetcher_unittest.cc
index 874ef2e..5d67570 100644
--- a/libcurl_http_fetcher_unittest.cc
+++ b/libcurl_http_fetcher_unittest.cc
@@ -100,7 +100,6 @@
 
   libcurl_fetcher_.BeginTransfer("https://An-uNres0lvable-uRl.invalid");
 
-#ifdef __ANDROID__
   // It's slower on Android that libcurl handle may not finish within 1 cycle.
   // Will need to wait for more cycles until it finishes. Original test didn't
   // correctly handle when we need to re-watch libcurl fds.
@@ -108,10 +107,7 @@
          libcurl_fetcher_.GetAuxiliaryErrorCode() == ErrorCode::kSuccess) {
     loop_.RunOnce(true);
   }
-#else
-  // The first time it can't resolve.
-  loop_.RunOnce(true);
-#endif
+
   EXPECT_EQ(libcurl_fetcher_.GetAuxiliaryErrorCode(),
             ErrorCode::kUnresolvedHostError);
 
@@ -141,7 +137,6 @@
   // easier to mock the part that depends on internet connectivity.
   libcurl_fetcher_.BeginTransfer("https://An-uNres0lvable-uRl.invalid");
 
-#ifdef __ANDROID__
   // It's slower on Android that libcurl handle may not finish within 1 cycle.
   // Will need to wait for more cycles until it finishes. Original test didn't
   // correctly handle when we need to re-watch libcurl fds.
@@ -149,10 +144,7 @@
          libcurl_fetcher_.GetAuxiliaryErrorCode() == ErrorCode::kSuccess) {
     loop_.RunOnce(true);
   }
-#else
-  // The first time it can't resolve.
-  loop_.RunOnce(true);
-#endif
+
   EXPECT_EQ(libcurl_fetcher_.GetAuxiliaryErrorCode(),
             ErrorCode::kUnresolvedHostError);
 
@@ -165,7 +157,6 @@
           [this]() { libcurl_fetcher_.http_response_code_ = 0; }));
   libcurl_fetcher_.transfer_size_ = 10;
 
-#ifdef __ANDROID__
   // It's slower on Android that libcurl handle may not finish within 1 cycle.
   // Will need to wait for more cycles until it finishes. Original test didn't
   // correctly handle when we need to re-watch libcurl fds.
@@ -173,11 +164,7 @@
                                      ErrorCode::kUnresolvedHostError) {
     loop_.RunOnce(true);
   }
-#else
-  // This time the host is resolved. But after that again we can't resolve
-  // anymore (See above).
-  loop_.RunOnce(true);
-#endif
+
   EXPECT_EQ(libcurl_fetcher_.GetAuxiliaryErrorCode(),
             ErrorCode::kUnresolvedHostRecovered);
 
diff --git a/logging.cc b/logging.cc
index 6320e36..012feee 100644
--- a/logging.cc
+++ b/logging.cc
@@ -79,7 +79,11 @@
   if (log_to_file) {
     log_file = SetupLogFile(kSystemLogsRoot);
     log_settings.delete_old = logging::APPEND_TO_OLD_LOG_FILE;
+#if BASE_VER < 780000
     log_settings.log_file = log_file.c_str();
+#else
+    log_settings.log_file_path = log_file.c_str();
+#endif
   }
   logging::InitLogging(log_settings);
 }
diff --git a/metrics_constants.h b/metrics_constants.h
index db21d90..679680c 100644
--- a/metrics_constants.h
+++ b/metrics_constants.h
@@ -106,7 +106,7 @@
   kUpdateCanceled,              // Update canceled by the user.
   kUpdateSucceededNotActive,    // Update succeeded but the new slot is not
                                 // active.
-
+  kUpdateSkipped,               // Current update skipped.
   kNumConstants,
 
   kUnset = -1
diff --git a/metrics_reporter_omaha.cc b/metrics_reporter_omaha.cc
index fb4e4ce..0cf0e59 100644
--- a/metrics_reporter_omaha.cc
+++ b/metrics_reporter_omaha.cc
@@ -146,8 +146,6 @@
 
 void MetricsReporterOmaha::ReportDailyMetrics(base::TimeDelta os_age) {
   string metric = metrics::kMetricDailyOSAgeDays;
-  LOG(INFO) << "Uploading " << utils::FormatTimeDelta(os_age) << " for metric "
-            << metric;
   metrics_lib_->SendToUMA(metric,
                           static_cast<int>(os_age.InDays()),
                           0,       // min: 0 days
@@ -168,20 +166,17 @@
     metric = metrics::kMetricCheckResult;
     value = static_cast<int>(result);
     max_value = static_cast<int>(metrics::CheckResult::kNumConstants) - 1;
-    LOG(INFO) << "Sending " << value << " for metric " << metric << " (enum)";
     metrics_lib_->SendEnumToUMA(metric, value, max_value);
   }
   if (reaction != metrics::CheckReaction::kUnset) {
     metric = metrics::kMetricCheckReaction;
     value = static_cast<int>(reaction);
     max_value = static_cast<int>(metrics::CheckReaction::kNumConstants) - 1;
-    LOG(INFO) << "Sending " << value << " for metric " << metric << " (enum)";
     metrics_lib_->SendEnumToUMA(metric, value, max_value);
   }
   if (download_error_code != metrics::DownloadErrorCode::kUnset) {
     metric = metrics::kMetricCheckDownloadErrorCode;
     value = static_cast<int>(download_error_code);
-    LOG(INFO) << "Sending " << value << " for metric " << metric << " (sparse)";
     metrics_lib_->SendSparseToUMA(metric, value);
   }
 
@@ -191,8 +186,6 @@
           kPrefsMetricsCheckLastReportingTime,
           &time_since_last)) {
     metric = metrics::kMetricCheckTimeSinceLastCheckMinutes;
-    LOG(INFO) << "Sending " << utils::FormatTimeDelta(time_since_last)
-              << " for metric " << metric;
     metrics_lib_->SendToUMA(metric,
                             time_since_last.InMinutes(),
                             0,             // min: 0 min
@@ -205,8 +198,6 @@
   if (metrics_utils::MonotonicDurationHelper(
           system_state, &uptime_since_last_storage, &uptime_since_last)) {
     metric = metrics::kMetricCheckTimeSinceLastCheckUptimeMinutes;
-    LOG(INFO) << "Sending " << utils::FormatTimeDelta(uptime_since_last)
-              << " for metric " << metric;
     metrics_lib_->SendToUMA(metric,
                             uptime_since_last.InMinutes(),
                             0,             // min: 0 min
@@ -221,13 +212,9 @@
     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);
       }
     }
@@ -239,8 +226,6 @@
   metrics::AttemptResult attempt_result =
       metrics::AttemptResult::kAbnormalTermination;
 
-  LOG(INFO) << "Uploading " << static_cast<int>(attempt_result)
-            << " for metric " << metric;
   metrics_lib_->SendEnumToUMA(
       metric,
       static_cast<int>(attempt_result),
@@ -257,7 +242,6 @@
     metrics::AttemptResult attempt_result,
     ErrorCode internal_error_code) {
   string metric = metrics::kMetricAttemptNumber;
-  LOG(INFO) << "Uploading " << attempt_number << " for metric " << metric;
   metrics_lib_->SendToUMA(metric,
                           attempt_number,
                           0,    // min: 0 attempts
@@ -265,13 +249,9 @@
                           50);  // num_buckets
 
   metric = metrics::kMetricAttemptPayloadType;
-  LOG(INFO) << "Uploading " << utils::ToString(payload_type) << " for metric "
-            << metric;
   metrics_lib_->SendEnumToUMA(metric, payload_type, kNumPayloadTypes);
 
   metric = metrics::kMetricAttemptDurationMinutes;
-  LOG(INFO) << "Uploading " << utils::FormatTimeDelta(duration)
-            << " for metric " << metric;
   metrics_lib_->SendToUMA(metric,
                           duration.InMinutes(),
                           0,             // min: 0 min
@@ -279,8 +259,6 @@
                           50);           // num_buckets
 
   metric = metrics::kMetricAttemptDurationUptimeMinutes;
-  LOG(INFO) << "Uploading " << utils::FormatTimeDelta(duration_uptime)
-            << " for metric " << metric;
   metrics_lib_->SendToUMA(metric,
                           duration_uptime.InMinutes(),
                           0,             // min: 0 min
@@ -289,7 +267,6 @@
 
   metric = metrics::kMetricAttemptPayloadSizeMiB;
   int64_t payload_size_mib = payload_size / kNumBytesInOneMiB;
-  LOG(INFO) << "Uploading " << payload_size_mib << " for metric " << metric;
   metrics_lib_->SendToUMA(metric,
                           payload_size_mib,
                           0,     // min: 0 MiB
@@ -297,8 +274,6 @@
                           50);   // num_buckets
 
   metric = metrics::kMetricAttemptResult;
-  LOG(INFO) << "Uploading " << static_cast<int>(attempt_result)
-            << " for metric " << metric;
   metrics_lib_->SendEnumToUMA(
       metric,
       static_cast<int>(attempt_result),
@@ -314,8 +289,6 @@
           kPrefsMetricsAttemptLastReportingTime,
           &time_since_last)) {
     metric = metrics::kMetricAttemptTimeSinceLastAttemptMinutes;
-    LOG(INFO) << "Sending " << utils::FormatTimeDelta(time_since_last)
-              << " for metric " << metric;
     metrics_lib_->SendToUMA(metric,
                             time_since_last.InMinutes(),
                             0,             // min: 0 min
@@ -328,8 +301,6 @@
   if (metrics_utils::MonotonicDurationHelper(
           system_state, &uptime_since_last_storage, &uptime_since_last)) {
     metric = metrics::kMetricAttemptTimeSinceLastAttemptUptimeMinutes;
-    LOG(INFO) << "Sending " << utils::FormatTimeDelta(uptime_since_last)
-              << " for metric " << metric;
     metrics_lib_->SendToUMA(metric,
                             uptime_since_last.InMinutes(),
                             0,             // min: 0 min
@@ -347,8 +318,6 @@
   string metric = metrics::kMetricAttemptPayloadBytesDownloadedMiB;
   int64_t payload_bytes_downloaded_mib =
       payload_bytes_downloaded / kNumBytesInOneMiB;
-  LOG(INFO) << "Uploading " << payload_bytes_downloaded_mib << " for metric "
-            << metric;
   metrics_lib_->SendToUMA(metric,
                           payload_bytes_downloaded_mib,
                           0,     // min: 0 MiB
@@ -357,8 +326,6 @@
 
   metric = metrics::kMetricAttemptPayloadDownloadSpeedKBps;
   int64_t payload_download_speed_kbps = payload_download_speed_bps / 1000;
-  LOG(INFO) << "Uploading " << payload_download_speed_kbps << " for metric "
-            << metric;
   metrics_lib_->SendToUMA(metric,
                           payload_download_speed_kbps,
                           0,          // min: 0 kB/s
@@ -366,20 +333,15 @@
                           50);        // num_buckets
 
   metric = metrics::kMetricAttemptDownloadSource;
-  LOG(INFO) << "Uploading " << download_source << " for metric " << metric;
   metrics_lib_->SendEnumToUMA(metric, download_source, kNumDownloadSources);
 
   if (payload_download_error_code != metrics::DownloadErrorCode::kUnset) {
     metric = metrics::kMetricAttemptDownloadErrorCode;
-    LOG(INFO) << "Uploading " << static_cast<int>(payload_download_error_code)
-              << " for metric " << metric << " (sparse)";
     metrics_lib_->SendSparseToUMA(
         metric, static_cast<int>(payload_download_error_code));
   }
 
   metric = metrics::kMetricAttemptConnectionType;
-  LOG(INFO) << "Uploading " << static_cast<int>(connection_type)
-            << " for metric " << metric;
   metrics_lib_->SendEnumToUMA(
       metric,
       static_cast<int>(connection_type),
@@ -399,7 +361,6 @@
     int url_switch_count) {
   string metric = metrics::kMetricSuccessfulUpdatePayloadSizeMiB;
   int64_t mbs = payload_size / kNumBytesInOneMiB;
-  LOG(INFO) << "Uploading " << mbs << " (MiBs) for metric " << metric;
   metrics_lib_->SendToUMA(metric,
                           mbs,
                           0,     // min: 0 MiB
@@ -429,7 +390,6 @@
     }
 
     if (mbs > 0) {
-      LOG(INFO) << "Uploading " << mbs << " (MiBs) for metric " << metric;
       metrics_lib_->SendToUMA(metric,
                               mbs,
                               0,     // min: 0 MiB
@@ -439,8 +399,6 @@
   }
 
   metric = metrics::kMetricSuccessfulUpdateDownloadSourcesUsed;
-  LOG(INFO) << "Uploading 0x" << std::hex << download_sources_used
-            << " (bit flags) for metric " << metric;
   metrics_lib_->SendToUMA(metric,
                           download_sources_used,
                           0,                               // min
@@ -448,8 +406,6 @@
                           1 << kNumDownloadSources);       // num_buckets
 
   metric = metrics::kMetricSuccessfulUpdateDownloadOverheadPercentage;
-  LOG(INFO) << "Uploading " << download_overhead_percentage << "% for metric "
-            << metric;
   metrics_lib_->SendToUMA(metric,
                           download_overhead_percentage,
                           0,     // min: 0% overhead
@@ -457,8 +413,6 @@
                           50);   // num_buckets
 
   metric = metrics::kMetricSuccessfulUpdateUrlSwitchCount;
-  LOG(INFO) << "Uploading " << url_switch_count << " (count) for metric "
-            << metric;
   metrics_lib_->SendToUMA(metric,
                           url_switch_count,
                           0,    // min: 0 URL switches
@@ -466,8 +420,6 @@
                           50);  // num_buckets
 
   metric = metrics::kMetricSuccessfulUpdateTotalDurationMinutes;
-  LOG(INFO) << "Uploading " << utils::FormatTimeDelta(total_duration)
-            << " for metric " << metric;
   metrics_lib_->SendToUMA(metric,
                           static_cast<int>(total_duration.InMinutes()),
                           0,              // min: 0 min
@@ -475,8 +427,6 @@
                           50);            // num_buckets
 
   metric = metrics::kMetricSuccessfulUpdateTotalDurationUptimeMinutes;
-  LOG(INFO) << "Uploading " << utils::FormatTimeDelta(total_duration_uptime)
-            << " for metric " << metric;
   metrics_lib_->SendToUMA(metric,
                           static_cast<int>(total_duration_uptime.InMinutes()),
                           0,             // min: 0 min
@@ -484,8 +434,6 @@
                           50);           // num_buckets
 
   metric = metrics::kMetricSuccessfulUpdateRebootCount;
-  LOG(INFO) << "Uploading reboot count of " << reboot_count << " for metric "
-            << metric;
   metrics_lib_->SendToUMA(metric,
                           reboot_count,
                           0,    // min: 0 reboots
@@ -494,8 +442,6 @@
 
   metric = metrics::kMetricSuccessfulUpdatePayloadType;
   metrics_lib_->SendEnumToUMA(metric, payload_type, kNumPayloadTypes);
-  LOG(INFO) << "Uploading " << utils::ToString(payload_type) << " for metric "
-            << metric;
 
   metric = metrics::kMetricSuccessfulUpdateAttemptCount;
   metrics_lib_->SendToUMA(metric,
@@ -503,11 +449,8 @@
                           1,    // min: 1 attempt
                           50,   // max: 50 attempts
                           50);  // num_buckets
-  LOG(INFO) << "Uploading " << attempt_count << " for metric " << metric;
 
   metric = metrics::kMetricSuccessfulUpdateUpdatesAbandonedCount;
-  LOG(INFO) << "Uploading " << updates_abandoned_count << " (count) for metric "
-            << metric;
   metrics_lib_->SendToUMA(metric,
                           updates_abandoned_count,
                           0,    // min: 0 counts
@@ -519,7 +462,6 @@
     metrics::RollbackResult result) {
   string metric = metrics::kMetricRollbackResult;
   int value = static_cast<int>(result);
-  LOG(INFO) << "Sending " << value << " for metric " << metric << " (enum)";
   metrics_lib_->SendEnumToUMA(
       metric, value, static_cast<int>(metrics::RollbackResult::kNumConstants));
 }
@@ -530,7 +472,6 @@
   string metric = metrics::kMetricEnterpriseRollbackSuccess;
   if (!success)
     metric = metrics::kMetricEnterpriseRollbackFailure;
-  LOG(INFO) << "Sending " << value << " for metric " << metric;
   metrics_lib_->SendSparseToUMA(metric, value);
 }
 
@@ -547,8 +488,6 @@
     case ServerToCheck::kNone:
       return;
   }
-  LOG(INFO) << "Uploading " << static_cast<int>(result) << " for metric "
-            << metric;
   metrics_lib_->SendEnumToUMA(
       metric,
       static_cast<int>(result),
@@ -562,9 +501,6 @@
                           1,   // min value
                           50,  // max value
                           kNumDefaultUmaBuckets);
-
-  LOG(INFO) << "Uploading " << target_attempt << " (count) for metric "
-            << metric;
 }
 
 void MetricsReporterOmaha::ReportTimeToReboot(int time_to_reboot_minutes) {
@@ -574,9 +510,6 @@
                           0,             // min: 0 minute
                           30 * 24 * 60,  // max: 1 month (approx)
                           kNumDefaultUmaBuckets);
-
-  LOG(INFO) << "Uploading " << time_to_reboot_minutes << " for metric "
-            << metric;
 }
 
 void MetricsReporterOmaha::ReportInstallDateProvisioningSource(int source,
@@ -588,7 +521,6 @@
 
 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));
@@ -600,18 +532,14 @@
     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);
 }
 
@@ -621,7 +549,6 @@
       has_time_restriction_policy
           ? metrics::kMetricSuccessfulUpdateDurationFromSeenTimeRestrictedDays
           : metrics::kMetricSuccessfulUpdateDurationFromSeenDays;
-  LOG(INFO) << "Sending " << time_to_update_days << " for metric " << metric;
 
   metrics_lib_->SendToUMA(metric,
                           time_to_update_days,
diff --git a/metrics_utils.cc b/metrics_utils.cc
index da3a2c3..2211a67 100644
--- a/metrics_utils.cc
+++ b/metrics_utils.cc
@@ -111,10 +111,6 @@
     case ErrorCode::kDownloadInvalidMetadataSignature:
     case ErrorCode::kOmahaResponseInvalid:
     case ErrorCode::kOmahaUpdateIgnoredPerPolicy:
-    // TODO(deymo): The next two items belong in their own category; they
-    // should not be counted as internal errors. b/27112092
-    case ErrorCode::kOmahaUpdateDeferredPerPolicy:
-    case ErrorCode::kNonCriticalUpdateInOOBE:
     case ErrorCode::kOmahaErrorInHTTPResponse:
     case ErrorCode::kDownloadMetadataSignatureMissingError:
     case ErrorCode::kOmahaUpdateDeferredForBackoff:
@@ -124,8 +120,13 @@
     case ErrorCode::kOmahaUpdateIgnoredOverCellular:
     case ErrorCode::kNoUpdate:
     case ErrorCode::kFirstActiveOmahaPingSentPersistenceError:
+    case ErrorCode::kPackageExcludedFromUpdate:
       return metrics::AttemptResult::kInternalError;
 
+    case ErrorCode::kOmahaUpdateDeferredPerPolicy:
+    case ErrorCode::kNonCriticalUpdateInOOBE:
+      return metrics::AttemptResult::kUpdateSkipped;
+
     // Special flags. These can't happen (we mask them out above) but
     // the compiler doesn't know that. Just break out so we can warn and
     // return |kInternalError|.
@@ -240,6 +241,7 @@
     case ErrorCode::kVerityCalculationError:
     case ErrorCode::kNotEnoughSpace:
     case ErrorCode::kDeviceCorrupted:
+    case ErrorCode::kPackageExcludedFromUpdate:
       break;
 
     // Special flags. These can't happen (we mask them out above) but
diff --git a/mock_update_attempter.h b/mock_update_attempter.h
index ad34802..96d93fd 100644
--- a/mock_update_attempter.h
+++ b/mock_update_attempter.h
@@ -30,16 +30,10 @@
  public:
   using UpdateAttempter::UpdateAttempter;
 
-  MOCK_METHOD9(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 rollback_data_save_requested,
-                    int rollback_allowed_milestones,
-                    bool obey_proxies,
-                    bool interactive));
+  MOCK_METHOD(void,
+              Update,
+              (const chromeos_update_manager::UpdateCheckParams& params),
+              (override));
 
   MOCK_METHOD1(GetStatus, bool(update_engine::UpdateEngineStatus* out_status));
 
diff --git a/omaha_request_action.cc b/omaha_request_action.cc
index 95e1250..161cf43 100644
--- a/omaha_request_action.cc
+++ b/omaha_request_action.cc
@@ -582,6 +582,7 @@
     LOG(INFO) << "Found package " << package.name;
 
     OmahaResponse::Package out_package;
+    out_package.app_id = app->id;
     out_package.can_exclude = can_exclude;
     for (const string& codebase : app->url_codebase) {
       if (codebase.empty()) {
@@ -631,6 +632,7 @@
 // Removes the candidate URLs which are excluded within packages, if all the
 // candidate URLs are excluded within a package, the package will be excluded.
 void ProcessExclusions(OmahaResponse* output_object,
+                       OmahaRequestParams* params,
                        ExcluderInterface* excluder) {
   for (auto package_it = output_object->packages.begin();
        package_it != output_object->packages.end();
@@ -657,6 +659,9 @@
     // If there are no candidate payload URLs, remove the package.
     if (package_it->payload_urls.empty()) {
       LOG(INFO) << "Excluding payload hash=" << package_it->hash;
+      // Need to set DLC as not updated so correct metrics can be sent when an
+      // update is completed.
+      params->SetDlcNoUpdate(package_it->app_id);
       package_it = output_object->packages.erase(package_it);
       continue;
     }
@@ -1023,6 +1028,7 @@
   if (!ParseResponse(&parser_data, &output_object, &completer))
     return;
   ProcessExclusions(&output_object,
+                    system_state_->request_params(),
                     system_state_->update_attempter()->GetExcluder());
   output_object.update_exists = true;
   SetOutputObject(output_object);
diff --git a/omaha_request_action_unittest.cc b/omaha_request_action_unittest.cc
index 6a0c213..61e988b 100644
--- a/omaha_request_action_unittest.cc
+++ b/omaha_request_action_unittest.cc
@@ -365,8 +365,6 @@
     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");
@@ -1528,6 +1526,7 @@
   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_lts_tag("unittest_hint&lt;");
   request_params_.set_hwid("<OEM MODEL>");
   fake_prefs_.SetString(kPrefsOmahaCohort, "evil\nstring");
   fake_prefs_.SetString(kPrefsOmahaCohortHint, "evil&string\\");
@@ -1547,6 +1546,8 @@
   EXPECT_EQ(string::npos, post_str.find("x86 generic<id"));
   EXPECT_NE(string::npos, post_str.find("unittest_track&amp;lt;"));
   EXPECT_EQ(string::npos, post_str.find("unittest_track&lt;"));
+  EXPECT_NE(string::npos, post_str.find("unittest_hint&amp;lt;"));
+  EXPECT_EQ(string::npos, post_str.find("unittest_hint&lt;"));
   EXPECT_NE(string::npos, post_str.find("&lt;OEM MODEL&gt;"));
   EXPECT_EQ(string::npos, post_str.find("<OEM MODEL>"));
   EXPECT_NE(string::npos, post_str.find("cohort=\"evil\nstring\""));
@@ -1601,8 +1602,6 @@
       string::npos);
   EXPECT_NE(post_str.find("hardware_class=\"OEM MODEL 09235 7471\""),
             string::npos);
-  EXPECT_NE(post_str.find("fw_version=\"ChromeOSFirmware.1.0\""), string::npos);
-  EXPECT_NE(post_str.find("ec_version=\"0X0A1\""), string::npos);
   // No <event> tag should be sent if we didn't reboot to an update.
   EXPECT_EQ(post_str.find("<event"), string::npos);
 }
@@ -1801,6 +1800,17 @@
   EXPECT_EQ(string::npos, post_str.find(omaha_cohort_hint));
 }
 
+TEST_F(OmahaRequestActionTest, TargetChannelHintTest) {
+  tuc_params_.http_response = fake_update_response_.GetNoUpdateResponse();
+  tuc_params_.expected_check_result = metrics::CheckResult::kNoUpdateAvailable;
+  tuc_params_.expected_check_reaction = metrics::CheckReaction::kUnset;
+  request_params_.set_lts_tag("hint>");
+
+  ASSERT_TRUE(TestUpdateCheck());
+
+  EXPECT_NE(string::npos, post_str.find("ltstag=\"hint&gt;\""));
+}
+
 void OmahaRequestActionTest::PingTest(bool ping_only) {
   NiceMock<MockPrefs> prefs;
   fake_system_state_.set_prefs(&prefs);
@@ -2776,8 +2786,8 @@
 }
 
 TEST_F(OmahaRequestActionTest, UpdateWithPartiallyExcludedDlcTest) {
-  request_params_.set_dlc_apps_params(
-      {{request_params_.GetDlcAppId(kDlcId1), {.name = kDlcId1}}});
+  const string kDlcAppId = request_params_.GetDlcAppId(kDlcId1);
+  request_params_.set_dlc_apps_params({{kDlcAppId, {.name = kDlcId1}}});
   fake_update_response_.dlc_app_update = true;
   tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
   // The first DLC candidate URL is excluded.
@@ -2790,11 +2800,12 @@
   // One candidate URL.
   EXPECT_EQ(response.packages[1].payload_urls.size(), 1u);
   EXPECT_TRUE(response.update_exists);
+  EXPECT_TRUE(request_params_.dlc_apps_params().at(kDlcAppId).updated);
 }
 
 TEST_F(OmahaRequestActionTest, UpdateWithExcludedDlcTest) {
-  request_params_.set_dlc_apps_params(
-      {{request_params_.GetDlcAppId(kDlcId1), {.name = kDlcId1}}});
+  const string kDlcAppId = request_params_.GetDlcAppId(kDlcId1);
+  request_params_.set_dlc_apps_params({{kDlcAppId, {.name = kDlcId1}}});
   fake_update_response_.dlc_app_update = true;
   tuc_params_.http_response = fake_update_response_.GetUpdateResponse();
   // Both DLC candidate URLs are excluded.
@@ -2805,6 +2816,7 @@
 
   EXPECT_EQ(response.packages.size(), 1u);
   EXPECT_TRUE(response.update_exists);
+  EXPECT_FALSE(request_params_.dlc_apps_params().at(kDlcAppId).updated);
 }
 
 TEST_F(OmahaRequestActionTest, UpdateWithDeprecatedDlcTest) {
diff --git a/omaha_request_builder_xml.cc b/omaha_request_builder_xml.cc
index e2857f1..6660afb 100644
--- a/omaha_request_builder_xml.cc
+++ b/omaha_request_builder_xml.cc
@@ -154,6 +154,11 @@
             app_body += " rollback_allowed=\"true\"";
           }
         }
+        if (!params_->lts_tag().empty()) {
+          app_body += base::StringPrintf(
+              " ltstag=\"%s\"",
+              XmlEncodeWithDefault(params_->lts_tag()).c_str());
+        }
         app_body += "></updatecheck>\n";
       }
 
@@ -184,17 +189,26 @@
       }
     }
   } else {
+    int event_result = event_->result;
     // The error code is an optional attribute so append it only if the result
     // is not success.
     string error_code;
-    if (event_->result != OmahaEvent::kResultSuccess) {
+    if (event_result != OmahaEvent::kResultSuccess) {
       error_code = base::StringPrintf(" errorcode=\"%d\"",
                                       static_cast<int>(event_->error_code));
+    } else if (app_data.is_dlc && !app_data.app_params.updated) {
+      // On a |OmahaEvent::kResultSuccess|, if the event is for an update
+      // completion and the App is a DLC, send error for excluded DLCs as they
+      // did not update.
+      event_result = OmahaEvent::Result::kResultError;
+      error_code = base::StringPrintf(
+          " errorcode=\"%d\"",
+          static_cast<int>(ErrorCode::kPackageExcludedFromUpdate));
     }
     app_body = base::StringPrintf(
         "        <event eventtype=\"%d\" eventresult=\"%d\"%s></event>\n",
         event_->type,
-        event_->result,
+        event_result,
         error_code.c_str());
   }
 
@@ -354,8 +368,6 @@
       // DLC excluded for installs and updates.
       (app_data.is_dlc ? "" :
       "lang=\"" + XmlEncodeWithDefault(params_->app_lang(), "en-US") + "\" " +
-      "fw_version=\"" + XmlEncodeWithDefault(params_->fw_version()) + "\" " +
-      "ec_version=\"" + XmlEncodeWithDefault(params_->ec_version()) + "\" " +
       requisition_arg) +
 
       ">\n" +
diff --git a/omaha_request_builder_xml_unittest.cc b/omaha_request_builder_xml_unittest.cc
index 017acec..042d991 100644
--- a/omaha_request_builder_xml_unittest.cc
+++ b/omaha_request_builder_xml_unittest.cc
@@ -108,8 +108,6 @@
   // in fact present in the <app ...></app>.
   const string app = omaha_request.GetApp(dlc_app_data);
   EXPECT_NE(string::npos, app.find("lang="));
-  EXPECT_NE(string::npos, app.find("fw_version="));
-  EXPECT_NE(string::npos, app.find("ec_version="));
   EXPECT_NE(string::npos, app.find("requisition="));
 }
 
@@ -132,8 +130,6 @@
   // fact not present in the <app ...></app>.
   const string app = omaha_request.GetApp(dlc_app_data);
   EXPECT_EQ(string::npos, app.find("lang="));
-  EXPECT_EQ(string::npos, app.find("fw_version="));
-  EXPECT_EQ(string::npos, app.find("ec_version="));
   EXPECT_EQ(string::npos, app.find("requisition="));
 }
 
@@ -148,10 +144,10 @@
                                        0,
                                        fake_system_state_.prefs(),
                                        ""};
-  const string request_xml = omaha_request.GetRequest();
+  const string kRequestXml = omaha_request.GetRequest();
   const string key = "requestid";
   const string request_id =
-      FindAttributeKeyValueInXml(request_xml, key, kGuidSize);
+      FindAttributeKeyValueInXml(kRequestXml, key, kGuidSize);
   // A valid |request_id| is either a GUID version 4 or empty string.
   if (!request_id.empty())
     EXPECT_TRUE(base::IsValidGUID(request_id));
@@ -169,10 +165,10 @@
                                        0,
                                        fake_system_state_.prefs(),
                                        gen_session_id};
-  const string request_xml = omaha_request.GetRequest();
+  const string kRequestXml = omaha_request.GetRequest();
   const string key = "sessionid";
   const string session_id =
-      FindAttributeKeyValueInXml(request_xml, key, kGuidSize);
+      FindAttributeKeyValueInXml(kRequestXml, key, kGuidSize);
   // A valid |session_id| is either a GUID version 4 or empty string.
   if (!session_id.empty()) {
     EXPECT_TRUE(base::IsValidGUID(session_id));
@@ -191,9 +187,9 @@
                                        0,
                                        fake_system_state_.prefs(),
                                        ""};
-  const string request_xml = omaha_request.GetRequest();
-  EXPECT_EQ(1, CountSubstringInString(request_xml, "<updatecheck"))
-      << request_xml;
+  const string kRequestXml = omaha_request.GetRequest();
+  EXPECT_EQ(1, CountSubstringInString(kRequestXml, "<updatecheck"))
+      << kRequestXml;
 }
 
 TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlPlatformUpdateWithDlcsTest) {
@@ -210,9 +206,9 @@
                                        0,
                                        fake_system_state_.prefs(),
                                        ""};
-  const string request_xml = omaha_request.GetRequest();
-  EXPECT_EQ(3, CountSubstringInString(request_xml, "<updatecheck"))
-      << request_xml;
+  const string kRequestXml = omaha_request.GetRequest();
+  EXPECT_EQ(3, CountSubstringInString(kRequestXml, "<updatecheck"))
+      << kRequestXml;
 }
 
 TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlDlcInstallationTest) {
@@ -231,25 +227,25 @@
                                        0,
                                        fake_system_state_.prefs(),
                                        ""};
-  const string request_xml = omaha_request.GetRequest();
-  EXPECT_EQ(2, CountSubstringInString(request_xml, "<updatecheck"))
-      << request_xml;
+  const string kRequestXml = omaha_request.GetRequest();
+  EXPECT_EQ(2, CountSubstringInString(kRequestXml, "<updatecheck"))
+      << kRequestXml;
 
-  auto FindAppId = [request_xml](size_t pos) -> size_t {
-    return request_xml.find("<app appid", pos);
+  auto FindAppId = [kRequestXml](size_t pos) -> size_t {
+    return kRequestXml.find("<app appid", pos);
   };
   // Skip over the Platform AppID, which is always first.
   size_t pos = FindAppId(0);
   for (auto&& _ : dlcs) {
     (void)_;
-    EXPECT_NE(string::npos, (pos = FindAppId(pos + 1))) << request_xml;
+    EXPECT_NE(string::npos, (pos = FindAppId(pos + 1))) << kRequestXml;
     const string dlc_app_id_version = FindAttributeKeyValueInXml(
-        request_xml.substr(pos), "version", string(kNoVersion).size());
+        kRequestXml.substr(pos), "version", string(kNoVersion).size());
     EXPECT_EQ(kNoVersion, dlc_app_id_version);
 
     const string false_str = "false";
     const string dlc_app_id_delta_okay = FindAttributeKeyValueInXml(
-        request_xml.substr(pos), "delta_okay", false_str.length());
+        kRequestXml.substr(pos), "delta_okay", false_str.length());
     EXPECT_EQ(false_str, dlc_app_id_delta_okay);
   }
 }
@@ -267,8 +263,8 @@
                                        0,
                                        fake_system_state_.prefs(),
                                        ""};
-  const string request_xml = omaha_request.GetRequest();
-  EXPECT_EQ(0, CountSubstringInString(request_xml, "<ping")) << request_xml;
+  const string kRequestXml = omaha_request.GetRequest();
+  EXPECT_EQ(0, CountSubstringInString(kRequestXml, "<ping")) << kRequestXml;
 }
 
 TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlDlcPingRollCallNoActive) {
@@ -289,9 +285,9 @@
                                        0,
                                        fake_system_state_.prefs(),
                                        ""};
-  const string request_xml = omaha_request.GetRequest();
-  EXPECT_EQ(1, CountSubstringInString(request_xml, "<ping rd=\"36\""))
-      << request_xml;
+  const string kRequestXml = omaha_request.GetRequest();
+  EXPECT_EQ(1, CountSubstringInString(kRequestXml, "<ping rd=\"36\""))
+      << kRequestXml;
 }
 
 TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlDlcPingRollCallAndActive) {
@@ -313,10 +309,93 @@
                                        0,
                                        fake_system_state_.prefs(),
                                        ""};
-  const string request_xml = omaha_request.GetRequest();
+  const string kRequestXml = omaha_request.GetRequest();
   EXPECT_EQ(1,
-            CountSubstringInString(request_xml,
+            CountSubstringInString(kRequestXml,
                                    "<ping active=\"1\" ad=\"25\" rd=\"36\""))
-      << request_xml;
+      << kRequestXml;
+}
+
+TEST_F(OmahaRequestBuilderXmlTest, GetRequestXmlUpdateCompleteEvent) {
+  OmahaRequestParams omaha_request_params{&fake_system_state_};
+  OmahaEvent event(OmahaEvent::kTypeUpdateComplete);
+  OmahaRequestBuilderXml omaha_request{&event,
+                                       &omaha_request_params,
+                                       false,
+                                       false,
+                                       0,
+                                       0,
+                                       0,
+                                       fake_system_state_.prefs(),
+                                       ""};
+  const string kRequestXml = omaha_request.GetRequest();
+  LOG(INFO) << kRequestXml;
+  EXPECT_EQ(
+      1,
+      CountSubstringInString(
+          kRequestXml, "<event eventtype=\"3\" eventresult=\"1\"></event>"))
+      << kRequestXml;
+}
+
+TEST_F(OmahaRequestBuilderXmlTest,
+       GetRequestXmlUpdateCompleteEventSomeDlcsExcluded) {
+  OmahaRequestParams omaha_request_params{&fake_system_state_};
+  omaha_request_params.set_dlc_apps_params({
+      {omaha_request_params.GetDlcAppId("dlc_1"), {.updated = true}},
+      {omaha_request_params.GetDlcAppId("dlc_2"), {.updated = false}},
+  });
+  OmahaEvent event(OmahaEvent::kTypeUpdateComplete);
+  OmahaRequestBuilderXml omaha_request{&event,
+                                       &omaha_request_params,
+                                       false,
+                                       false,
+                                       0,
+                                       0,
+                                       0,
+                                       fake_system_state_.prefs(),
+                                       ""};
+  const string kRequestXml = omaha_request.GetRequest();
+  EXPECT_EQ(
+      2,
+      CountSubstringInString(
+          kRequestXml, "<event eventtype=\"3\" eventresult=\"1\"></event>"))
+      << kRequestXml;
+  EXPECT_EQ(
+      1,
+      CountSubstringInString(
+          kRequestXml,
+          "<event eventtype=\"3\" eventresult=\"0\" errorcode=\"62\"></event>"))
+      << kRequestXml;
+}
+
+TEST_F(OmahaRequestBuilderXmlTest,
+       GetRequestXmlUpdateCompleteEventAllDlcsExcluded) {
+  OmahaRequestParams omaha_request_params{&fake_system_state_};
+  omaha_request_params.set_dlc_apps_params({
+      {omaha_request_params.GetDlcAppId("dlc_1"), {.updated = false}},
+      {omaha_request_params.GetDlcAppId("dlc_2"), {.updated = false}},
+  });
+  OmahaEvent event(OmahaEvent::kTypeUpdateComplete);
+  OmahaRequestBuilderXml omaha_request{&event,
+                                       &omaha_request_params,
+                                       false,
+                                       false,
+                                       0,
+                                       0,
+                                       0,
+                                       fake_system_state_.prefs(),
+                                       ""};
+  const string kRequestXml = omaha_request.GetRequest();
+  EXPECT_EQ(
+      1,
+      CountSubstringInString(
+          kRequestXml, "<event eventtype=\"3\" eventresult=\"1\"></event>"))
+      << kRequestXml;
+  EXPECT_EQ(
+      2,
+      CountSubstringInString(
+          kRequestXml,
+          "<event eventtype=\"3\" eventresult=\"0\" errorcode=\"62\"></event>"))
+      << kRequestXml;
 }
 }  // namespace chromeos_update_engine
diff --git a/omaha_request_params.cc b/omaha_request_params.cc
index 8a2e3dc..5a48720 100644
--- a/omaha_request_params.cc
+++ b/omaha_request_params.cc
@@ -37,9 +37,11 @@
 #include "update_engine/common/platform_constants.h"
 #include "update_engine/common/utils.h"
 #include "update_engine/system_state.h"
+#include "update_engine/update_manager/policy.h"
 
 #define CALL_MEMBER_FN(object, member) ((object).*(member))
 
+using chromeos_update_manager::UpdateCheckParams;
 using std::string;
 
 namespace chromeos_update_engine {
@@ -59,9 +61,9 @@
     test::SetImagePropertiesRootPrefix(nullptr);
 }
 
-bool OmahaRequestParams::Init(const string& in_app_version,
-                              const string& in_update_url,
-                              bool in_interactive) {
+bool OmahaRequestParams::Init(const string& app_version,
+                              const string& update_url,
+                              const UpdateCheckParams& params) {
   LOG(INFO) << "Initializing parameters for this update attempt";
   image_props_ = LoadImageProperties(system_state_);
   mutable_image_props_ = LoadMutableImageProperties(system_state_);
@@ -77,23 +79,19 @@
 
   os_platform_ = constants::kOmahaPlatformName;
   if (!image_props_.system_version.empty()) {
-    if (in_app_version == "ForcedUpdate") {
-      image_props_.system_version = in_app_version;
+    if (app_version == "ForcedUpdate") {
+      image_props_.system_version = app_version;
     }
     os_version_ = image_props_.system_version;
   } else {
     os_version_ = OmahaRequestParams::kOsVersion;
   }
-  if (!in_app_version.empty())
-    image_props_.version = in_app_version;
+  if (!app_version.empty())
+    image_props_.version = app_version;
 
   os_sp_ = image_props_.version + "_" + GetMachineType();
   app_lang_ = "en-US";
   hwid_ = system_state_->hardware()->GetHardwareClass();
-  if (CollectECFWVersions()) {
-    fw_version_ = system_state_->hardware()->GetFirmwareVersion();
-    ec_version_ = system_state_->hardware()->GetECVersion();
-  }
   device_requisition_ = system_state_->hardware()->GetDeviceRequisition();
 
   if (image_props_.current_channel == mutable_image_props_.target_channel) {
@@ -115,17 +113,51 @@
     delta_okay_ = false;
   }
 
-  if (in_update_url.empty())
+  if (update_url.empty())
     update_url_ = image_props_.omaha_url;
   else
-    update_url_ = in_update_url;
+    update_url_ = update_url;
 
   // Set the interactive flag accordingly.
-  interactive_ = in_interactive;
+  interactive_ = params.interactive;
 
   dlc_apps_params_.clear();
   // Set false so it will do update by default.
   is_install_ = false;
+
+  target_version_prefix_ = params.target_version_prefix;
+
+  lts_tag_ = params.lts_tag;
+
+  rollback_allowed_ = params.rollback_allowed;
+
+  // Set whether saving data over rollback is requested.
+  rollback_data_save_requested_ = params.rollback_data_save_requested;
+
+  // Set how many milestones of rollback are allowed.
+  rollback_allowed_milestones_ = params.rollback_allowed_milestones;
+
+  // Set the target channel, if one was provided.
+  if (params.target_channel.empty()) {
+    LOG(INFO) << "No target channel mandated by policy.";
+  } else {
+    LOG(INFO) << "Setting target channel as mandated: "
+              << params.target_channel;
+    string error_message;
+    if (!SetTargetChannel(params.target_channel,
+                          params.rollback_on_channel_downgrade,
+                          &error_message)) {
+      LOG(ERROR) << "Setting the channel failed: " << error_message;
+    }
+
+    // Since this is the beginning of a new attempt, update the download
+    // channel. The download channel won't be updated until the next attempt,
+    // even if target channel changes meanwhile, so that how we'll know if we
+    // should cancel the current download attempt if there's such a change in
+    // target channel.
+    UpdateDownloadChannel();
+  }
+
   return true;
 }
 
@@ -134,14 +166,6 @@
           update_url_ == image_props_.omaha_url);
 }
 
-bool OmahaRequestParams::CollectECFWVersions() const {
-  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);
-}
-
 bool OmahaRequestParams::SetTargetChannel(const string& new_target_channel,
                                           bool is_powerwash_allowed,
                                           string* error_message) {
diff --git a/omaha_request_params.h b/omaha_request_params.h
index 76fc806..ed3cc80 100644
--- a/omaha_request_params.h
+++ b/omaha_request_params.h
@@ -30,6 +30,7 @@
 #include "update_engine/common/constants.h"
 #include "update_engine/common/platform_constants.h"
 #include "update_engine/image_properties.h"
+#include "update_engine/update_manager/policy.h"
 
 // This gathers local system information and prepares info used by the
 // Omaha request action.
@@ -103,8 +104,6 @@
   }
   inline std::string app_lang() const { return app_lang_; }
   inline std::string hwid() const { return hwid_; }
-  inline std::string fw_version() const { return fw_version_; }
-  inline std::string ec_version() const { return ec_version_; }
   inline std::string device_requisition() const { return device_requisition_; }
 
   inline void set_app_version(const std::string& version) {
@@ -148,6 +147,10 @@
     return target_version_prefix_;
   }
 
+  inline std::string lts_tag() const { return lts_tag_; }
+
+  inline void set_lts_tag(const std::string& hint) { lts_tag_ = hint; }
+
   inline void set_rollback_allowed(bool rollback_allowed) {
     rollback_allowed_ = rollback_allowed;
   }
@@ -245,7 +248,7 @@
   // of the parameter. Returns true on success, false otherwise.
   bool Init(const std::string& in_app_version,
             const std::string& in_update_url,
-            bool in_interactive);
+            const chromeos_update_manager::UpdateCheckParams& params);
 
   // Permanently changes the release channel to |channel|. Performs a
   // powerwash, if required and allowed.
@@ -289,22 +292,19 @@
   }
   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;
   }
+  bool is_powerwash_allowed() {
+    return mutable_image_props_.is_powerwash_allowed;
+  }
+
   void set_device_requisition(const std::string& requisition) {
     device_requisition_ = requisition;
   }
 
  private:
   FRIEND_TEST(OmahaRequestParamsTest, ChannelIndexTest);
-  FRIEND_TEST(OmahaRequestParamsTest, CollectECFWVersionsTest);
   FRIEND_TEST(OmahaRequestParamsTest, IsValidChannelTest);
   FRIEND_TEST(OmahaRequestParamsTest, SetIsPowerwashAllowedTest);
   FRIEND_TEST(OmahaRequestParamsTest, SetTargetChannelInvalidTest);
@@ -327,10 +327,6 @@
   // i.e. index(target_channel) > index(current_channel).
   bool ToMoreStableChannel() const;
 
-  // Returns True if we should store the fw/ec versions based on our hwid_.
-  // Compares hwid to a set of prefixes in the allowlist.
-  bool CollectECFWVersions() const;
-
   // Gets the machine type (e.g. "i686").
   std::string GetMachineType() const;
 
@@ -367,14 +363,15 @@
   //   changed and cancel the current download attempt.
   std::string download_channel_;
 
+  // The value defining the parameters of the LTS (Long Term Support).
+  std::string lts_tag_;
+
   std::string hwid_;        // Hardware Qualification ID of the client
-  std::string fw_version_;  // Chrome OS Firmware Version.
-  std::string ec_version_;  // Chrome OS EC Version.
   // TODO(b:133324571) tracks removal of this field once it is no longer
   // needed in AU requests. Remove by October 1st 2019.
   std::string device_requisition_;  // Chrome OS Requisition type.
-  bool delta_okay_;         // If this client can accept a delta
-  bool interactive_;        // Whether this is a user-initiated update check
+  bool delta_okay_;                 // If this client can accept a delta
+  bool interactive_;  // Whether this is a user-initiated update check
 
   // The URL to send the Omaha request to.
   std::string update_url_;
diff --git a/omaha_request_params_unittest.cc b/omaha_request_params_unittest.cc
index bfcbc32..fcf8062 100644
--- a/omaha_request_params_unittest.cc
+++ b/omaha_request_params_unittest.cc
@@ -75,36 +75,36 @@
 }  // namespace
 
 TEST_F(OmahaRequestParamsTest, MissingChannelTest) {
-  EXPECT_TRUE(params_.Init("", "", false));
+  EXPECT_TRUE(params_.Init("", "", {}));
   // By default, if no channel is set, we should track the stable-channel.
   EXPECT_EQ("stable-channel", params_.target_channel());
 }
 
 TEST_F(OmahaRequestParamsTest, ForceVersionTest) {
-  EXPECT_TRUE(params_.Init("ForcedVersion", "", false));
+  EXPECT_TRUE(params_.Init("ForcedVersion", "", {}));
   EXPECT_EQ(string("ForcedVersion_") + GetMachineType(), params_.os_sp());
   EXPECT_EQ("ForcedVersion", params_.app_version());
 }
 
 TEST_F(OmahaRequestParamsTest, ForcedURLTest) {
-  EXPECT_TRUE(params_.Init("", "http://forced.google.com", false));
+  EXPECT_TRUE(params_.Init("", "http://forced.google.com", {}));
   EXPECT_EQ("http://forced.google.com", params_.update_url());
 }
 
 TEST_F(OmahaRequestParamsTest, MissingURLTest) {
-  EXPECT_TRUE(params_.Init("", "", false));
+  EXPECT_TRUE(params_.Init("", "", {}));
   EXPECT_EQ(constants::kOmahaDefaultProductionURL, params_.update_url());
 }
 
 TEST_F(OmahaRequestParamsTest, DeltaOKTest) {
-  EXPECT_TRUE(params_.Init("", "", false));
+  EXPECT_TRUE(params_.Init("", "", {}));
   EXPECT_TRUE(params_.delta_okay());
 }
 
 TEST_F(OmahaRequestParamsTest, NoDeltasTest) {
   ASSERT_TRUE(
       WriteFileString(tempdir_.GetPath().Append(".nodelta").value(), ""));
-  EXPECT_TRUE(params_.Init("", "", false));
+  EXPECT_TRUE(params_.Init("", "", {}));
   EXPECT_FALSE(params_.delta_okay());
 }
 
@@ -112,12 +112,12 @@
   {
     OmahaRequestParams params(&fake_system_state_);
     params.set_root(tempdir_.GetPath().value());
-    EXPECT_TRUE(params.Init("", "", false));
+    EXPECT_TRUE(params.Init("", "", {}));
     EXPECT_TRUE(params.SetTargetChannel("canary-channel", false, nullptr));
     EXPECT_FALSE(params.mutable_image_props_.is_powerwash_allowed);
   }
   params_.set_root(tempdir_.GetPath().value());
-  EXPECT_TRUE(params_.Init("", "", false));
+  EXPECT_TRUE(params_.Init("", "", {}));
   EXPECT_EQ("canary-channel", params_.target_channel());
   EXPECT_FALSE(params_.mutable_image_props_.is_powerwash_allowed);
 }
@@ -126,12 +126,12 @@
   {
     OmahaRequestParams params(&fake_system_state_);
     params.set_root(tempdir_.GetPath().value());
-    EXPECT_TRUE(params.Init("", "", false));
+    EXPECT_TRUE(params.Init("", "", {}));
     EXPECT_TRUE(params.SetTargetChannel("canary-channel", true, nullptr));
     EXPECT_TRUE(params.mutable_image_props_.is_powerwash_allowed);
   }
   params_.set_root(tempdir_.GetPath().value());
-  EXPECT_TRUE(params_.Init("", "", false));
+  EXPECT_TRUE(params_.Init("", "", {}));
   EXPECT_EQ("canary-channel", params_.target_channel());
   EXPECT_TRUE(params_.mutable_image_props_.is_powerwash_allowed);
 }
@@ -141,7 +141,7 @@
     OmahaRequestParams params(&fake_system_state_);
     params.set_root(tempdir_.GetPath().value());
     SetLockDown(true);
-    EXPECT_TRUE(params.Init("", "", false));
+    EXPECT_TRUE(params.Init("", "", {}));
     params.image_props_.allow_arbitrary_channels = false;
     string error_message;
     EXPECT_FALSE(
@@ -151,7 +151,7 @@
     EXPECT_FALSE(params.mutable_image_props_.is_powerwash_allowed);
   }
   params_.set_root(tempdir_.GetPath().value());
-  EXPECT_TRUE(params_.Init("", "", false));
+  EXPECT_TRUE(params_.Init("", "", {}));
   EXPECT_EQ("stable-channel", params_.target_channel());
   EXPECT_FALSE(params_.mutable_image_props_.is_powerwash_allowed);
 }
@@ -197,7 +197,7 @@
 
   // When set to a valid value while a change is already pending, it should
   // succeed.
-  params_.Init("", "", false);
+  params_.Init("", "", {});
   EXPECT_TRUE(params_.SetTargetChannel("beta-channel", true, nullptr));
   // The target channel should reflect the change, but the download channel
   // should continue to retain the old value ...
@@ -236,6 +236,13 @@
   EXPECT_FALSE(params_.ToMoreStableChannel());
 }
 
+TEST_F(OmahaRequestParamsTest, TargetChannelHintTest) {
+  EXPECT_TRUE(params_.Init("", "", {}));
+  const string kHint("foo-hint");
+  params_.set_lts_tag(kHint);
+  EXPECT_EQ(kHint, params_.lts_tag());
+}
+
 TEST_F(OmahaRequestParamsTest, ShouldPowerwashTest) {
   params_.mutable_image_props_.is_powerwash_allowed = false;
   EXPECT_FALSE(params_.ShouldPowerwash());
@@ -250,16 +257,8 @@
   EXPECT_TRUE(params_.ShouldPowerwash());
 }
 
-TEST_F(OmahaRequestParamsTest, CollectECFWVersionsTest) {
-  params_.hwid_ = string("STUMPY ALEX 12345");
-  EXPECT_FALSE(params_.CollectECFWVersions());
-
-  params_.hwid_ = string("SNOW 12345");
-  EXPECT_TRUE(params_.CollectECFWVersions());
-}
-
 TEST_F(OmahaRequestParamsTest, RequisitionIsSetTest) {
-  EXPECT_TRUE(params_.Init("", "", false));
+  EXPECT_TRUE(params_.Init("", "", {}));
   EXPECT_EQ("fake_requisition", params_.device_requisition());
 }
 }  // namespace chromeos_update_engine
diff --git a/omaha_response.h b/omaha_response.h
index 2b86fe7..77f9083 100644
--- a/omaha_response.h
+++ b/omaha_response.h
@@ -54,6 +54,8 @@
     // True if the payload can be excluded from updating if consistently faulty.
     // False if the payload is critical to update.
     bool can_exclude = false;
+    // The App ID associated with the package.
+    std::string app_id;
   };
   std::vector<Package> packages;
 
diff --git a/omaha_response_handler_action.cc b/omaha_response_handler_action.cc
index 040f8e7..92e0a72 100644
--- a/omaha_response_handler_action.cc
+++ b/omaha_response_handler_action.cc
@@ -188,10 +188,12 @@
   }
 
   // Powerwash if either the response requires it or the parameters indicated
-  // powerwash and we are downgrading the version.
+  // powerwash (usually because there was a channel downgrade) and we are
+  // downgrading the version. Enterprise rollback, indicated by
+  // |response.is_rollback| is dealt with separately above.
   if (response.powerwash_required) {
     install_plan_.powerwash_required = true;
-  } else if (params->ShouldPowerwash()) {
+  } else if (params->ShouldPowerwash() && !response.is_rollback) {
     base::Version new_version(response.version);
     base::Version current_version(params->app_version());
 
@@ -205,6 +207,10 @@
                    << " Current version number: " << params->app_version();
     } else if (new_version < current_version) {
       install_plan_.powerwash_required = true;
+      // Always try to preserve enrollment and wifi data for enrolled devices.
+      install_plan_.rollback_data_save_requested =
+          system_state_ && system_state_->device_policy() &&
+          system_state_->device_policy()->IsEnterpriseEnrolled();
     }
   }
 
diff --git a/omaha_response_handler_action_unittest.cc b/omaha_response_handler_action_unittest.cc
index 04cfa73..9613e8d 100644
--- a/omaha_response_handler_action_unittest.cc
+++ b/omaha_response_handler_action_unittest.cc
@@ -176,7 +176,7 @@
 }
 
 TEST_F(OmahaResponseHandlerActionTest, SimpleTest) {
-  test_utils::ScopedTempFile test_deadline_file(
+  ScopedTempFile test_deadline_file(
       "omaha_response_handler_action_unittest-XXXXXX");
   {
     OmahaResponse in;
@@ -532,6 +532,132 @@
 }
 
 TEST_F(OmahaResponseHandlerActionTest,
+       ChangeToMoreStableChannelButSameVersionTest) {
+  OmahaResponse in;
+  in.update_exists = true;
+  in.version = "12345.0.0.0";
+  in.packages.push_back({.payload_urls = {"https://ChannelDownVersionUp"},
+                         .size = 1,
+                         .hash = kPayloadHashHex});
+  in.more_info_url = "http://more/info";
+
+  // Create a uniquely named test directory.
+  base::ScopedTempDir tempdir;
+  ASSERT_TRUE(tempdir.CreateUniqueTempDir());
+
+  OmahaRequestParams params(&fake_system_state_);
+  fake_system_state_.fake_hardware()->SetIsOfficialBuild(false);
+  params.set_root(tempdir.GetPath().value());
+  params.set_current_channel("beta-channel");
+  EXPECT_TRUE(params.SetTargetChannel("stable-channel", true, nullptr));
+  params.UpdateDownloadChannel();
+  params.set_app_version("12345.0.0.0");
+
+  fake_system_state_.set_request_params(&params);
+  InstallPlan install_plan;
+  EXPECT_TRUE(DoTest(in, "", &install_plan));
+  EXPECT_FALSE(install_plan.powerwash_required);
+  EXPECT_FALSE(install_plan.rollback_data_save_requested);
+}
+
+// On an enrolled device, the rollback data restore should be attempted when
+// doing a powerwash and channel downgrade.
+TEST_F(OmahaResponseHandlerActionTest,
+       ChangeToMoreStableChannelEnrolledDataRestore) {
+  OmahaResponse in;
+  in.update_exists = true;
+  in.version = "12345.96.0.0";
+  in.packages.push_back({.payload_urls = {"https://ChannelDownEnrolled"},
+                         .size = 1,
+                         .hash = kPayloadHashHex});
+  in.more_info_url = "http://more/info";
+
+  // Create a uniquely named test directory.
+  base::ScopedTempDir tempdir;
+  ASSERT_TRUE(tempdir.CreateUniqueTempDir());
+
+  OmahaRequestParams params(&fake_system_state_);
+  fake_system_state_.fake_hardware()->SetIsOfficialBuild(true);
+  params.set_root(tempdir.GetPath().value());
+  params.set_current_channel("beta-channel");
+  EXPECT_TRUE(params.SetTargetChannel("stable-channel", true, nullptr));
+  params.UpdateDownloadChannel();
+  params.set_app_version("12347.48.0.0");
+
+  testing::NiceMock<policy::MockDevicePolicy> mock_device_policy;
+  EXPECT_CALL(mock_device_policy, IsEnterpriseEnrolled())
+      .WillOnce(Return(true));
+  fake_system_state_.set_device_policy(&mock_device_policy);
+
+  fake_system_state_.set_request_params(&params);
+  InstallPlan install_plan;
+  EXPECT_TRUE(DoTest(in, "", &install_plan));
+  EXPECT_TRUE(install_plan.rollback_data_save_requested);
+}
+
+// Never attempt rollback data restore if the device is not enrolled.
+TEST_F(OmahaResponseHandlerActionTest,
+       ChangeToMoreStableChannelUnenrolledNoDataRestore) {
+  OmahaResponse in;
+  in.update_exists = true;
+  in.version = "12345.96.0.0";
+  in.packages.push_back({.payload_urls = {"https://ChannelDownEnrolled"},
+                         .size = 1,
+                         .hash = kPayloadHashHex});
+  in.more_info_url = "http://more/info";
+
+  // Create a uniquely named test directory.
+  base::ScopedTempDir tempdir;
+  ASSERT_TRUE(tempdir.CreateUniqueTempDir());
+
+  OmahaRequestParams params(&fake_system_state_);
+  fake_system_state_.fake_hardware()->SetIsOfficialBuild(true);
+  params.set_root(tempdir.GetPath().value());
+  params.set_current_channel("beta-channel");
+  EXPECT_TRUE(params.SetTargetChannel("stable-channel", true, nullptr));
+  params.UpdateDownloadChannel();
+  params.set_app_version("12347.48.0.0");
+
+  testing::NiceMock<policy::MockDevicePolicy> mock_device_policy;
+  EXPECT_CALL(mock_device_policy, IsEnterpriseEnrolled())
+      .WillOnce(Return(false));
+  fake_system_state_.set_device_policy(&mock_device_policy);
+
+  fake_system_state_.set_request_params(&params);
+  InstallPlan install_plan;
+  EXPECT_TRUE(DoTest(in, "", &install_plan));
+  EXPECT_FALSE(install_plan.rollback_data_save_requested);
+}
+
+// Never attempt rollback data restore if powerwash is not allowed.
+TEST_F(OmahaResponseHandlerActionTest,
+       ChangeToMoreStableChannelNoPowerwashNoDataRestore) {
+  OmahaResponse in;
+  in.update_exists = true;
+  in.version = "12345.96.0.0";
+  in.packages.push_back(
+      {.payload_urls = {"https://URL"}, .size = 1, .hash = kPayloadHashHex});
+  in.more_info_url = "http://more/info";
+
+  // Create a uniquely named test directory.
+  base::ScopedTempDir tempdir;
+  ASSERT_TRUE(tempdir.CreateUniqueTempDir());
+
+  OmahaRequestParams params(&fake_system_state_);
+  fake_system_state_.fake_hardware()->SetIsOfficialBuild(true);
+  params.set_root(tempdir.GetPath().value());
+  params.set_current_channel("beta-channel");
+  EXPECT_TRUE(params.SetTargetChannel("stable-channel", false, nullptr));
+  params.UpdateDownloadChannel();
+  params.set_app_version("12347.48.0.0");
+
+  fake_system_state_.set_request_params(&params);
+  InstallPlan install_plan;
+  EXPECT_TRUE(DoTest(in, "", &install_plan));
+  EXPECT_FALSE(install_plan.rollback_data_save_requested);
+}
+
+TEST_F(OmahaResponseHandlerActionTest,
        ChangeToLessStableVersionAndChannelTest) {
   OmahaResponse in;
   in.update_exists = true;
diff --git a/p2p_manager_unittest.cc b/p2p_manager_unittest.cc
index 5771ec1..5510dd7 100644
--- a/p2p_manager_unittest.cc
+++ b/p2p_manager_unittest.cc
@@ -30,8 +30,13 @@
 #include <base/bind.h>
 #include <base/callback.h>
 #include <base/files/file_util.h>
+#if BASE_VER < 780000  // Android
 #include <base/message_loop/message_loop.h>
+#endif  // BASE_VER < 780000
 #include <base/strings/stringprintf.h>
+#if BASE_VER >= 780000  // CrOS
+#include <base/task/single_thread_task_executor.h>
+#endif  // BASE_VER >= 780000
 #include <brillo/asynchronous_signal_handler.h>
 #include <brillo/message_loops/base_message_loop.h>
 #include <brillo/message_loops/message_loop.h>
@@ -92,8 +97,13 @@
                                          TimeDelta::FromDays(5)));
   }
 
+#if BASE_VER < 780000  // Android
   base::MessageLoopForIO base_loop_;
   brillo::BaseMessageLoop loop_{&base_loop_};
+#else   // CrOS
+  base::SingleThreadTaskExecutor base_loop_{base::MessagePumpType::IO};
+  brillo::BaseMessageLoop loop_{base_loop_.task_runner()};
+#endif  // BASE_VER < 780000
   brillo::AsynchronousSignalHandler async_signal_handler_;
   Subprocess subprocess_;
 
diff --git a/payload_consumer/bzip_extent_writer_unittest.cc b/payload_consumer/bzip_extent_writer_unittest.cc
index 125e1e5..b587040 100644
--- a/payload_consumer/bzip_extent_writer_unittest.cc
+++ b/payload_consumer/bzip_extent_writer_unittest.cc
@@ -49,7 +49,7 @@
   void TearDown() override { fd_->Close(); }
 
   FileDescriptorPtr fd_;
-  test_utils::ScopedTempFile temp_file_{"BzipExtentWriterTest-file.XXXXXX"};
+  ScopedTempFile temp_file_{"BzipExtentWriterTest-file.XXXXXX"};
 };
 
 TEST_F(BzipExtentWriterTest, SimpleTest) {
diff --git a/payload_consumer/cached_file_descriptor_unittest.cc b/payload_consumer/cached_file_descriptor_unittest.cc
index d2965fc..b64420a 100644
--- a/payload_consumer/cached_file_descriptor_unittest.cc
+++ b/payload_consumer/cached_file_descriptor_unittest.cc
@@ -73,7 +73,7 @@
 
  protected:
   FileDescriptorPtr fd_{new EintrSafeFileDescriptor};
-  test_utils::ScopedTempFile temp_file_{"CachedFileDescriptor-file.XXXXXX"};
+  ScopedTempFile temp_file_{"CachedFileDescriptor-file.XXXXXX"};
   int value_{1};
   FileDescriptorPtr cfd_;
 };
diff --git a/payload_consumer/delta_performer.cc b/payload_consumer/delta_performer.cc
index e7ef8a3..a3989d6 100644
--- a/payload_consumer/delta_performer.cc
+++ b/payload_consumer/delta_performer.cc
@@ -277,15 +277,6 @@
 
 }  // namespace
 
-uint32_t DeltaPerformer::GetMinorVersion() const {
-  if (manifest_.has_minor_version()) {
-    return manifest_.minor_version();
-  }
-  return payload_->type == InstallPayloadType::kDelta
-             ? kMaxSupportedMinorPayloadVersion
-             : kFullPayloadMinorVersion;
-}
-
 bool DeltaPerformer::IsHeaderParsed() const {
   return metadata_size_ != 0;
 }
@@ -1091,13 +1082,6 @@
     }
   }
 
-  if (manifest_.has_old_rootfs_info() || manifest_.has_new_rootfs_info() ||
-      manifest_.has_old_kernel_info() || manifest_.has_new_kernel_info() ||
-      manifest_.install_operations_size() != 0 ||
-      manifest_.kernel_install_operations_size() != 0) {
-    LOG(ERROR) << "Manifest contains deprecated fields.";
-    return ErrorCode::kPayloadMismatchedType;
-  }
   ErrorCode error_code = CheckTimestampError();
   if (error_code != ErrorCode::kSuccess) {
     if (error_code == ErrorCode::kPayloadTimestampError) {
@@ -1294,6 +1278,13 @@
     return ErrorCode::kPayloadSizeMismatchError;
   }
 
+  // Verifies the payload hash.
+  TEST_AND_RETURN_VAL(ErrorCode::kDownloadPayloadVerificationError,
+                      !payload_hash_calculator_.raw_hash().empty());
+  TEST_AND_RETURN_VAL(
+      ErrorCode::kPayloadHashMismatchError,
+      payload_hash_calculator_.raw_hash() == update_check_response_hash);
+
   auto [payload_verifier, perform_verification] = CreatePayloadVerifier();
   if (!perform_verification) {
     LOG(WARNING) << "Not verifying signed delta payload -- missing public key.";
@@ -1304,13 +1295,6 @@
     return ErrorCode::kDownloadPayloadPubKeyVerificationError;
   }
 
-  // Verifies the payload hash.
-  TEST_AND_RETURN_VAL(ErrorCode::kDownloadPayloadVerificationError,
-                      !payload_hash_calculator_.raw_hash().empty());
-  TEST_AND_RETURN_VAL(
-      ErrorCode::kPayloadHashMismatchError,
-      payload_hash_calculator_.raw_hash() == update_check_response_hash);
-
   TEST_AND_RETURN_VAL(ErrorCode::kSignedDeltaPayloadExpectedError,
                       !signatures_message_data_.empty());
   brillo::Blob hash_data = signed_hash_calculator_.raw_hash();
diff --git a/payload_consumer/delta_performer.h b/payload_consumer/delta_performer.h
index 96bc849..4990bf8 100644
--- a/payload_consumer/delta_performer.h
+++ b/payload_consumer/delta_performer.h
@@ -170,9 +170,13 @@
   // Return true if header parsing is finished and no errors occurred.
   bool IsHeaderParsed() const;
 
-  // Returns the delta minor version. If this value is defined in the manifest,
-  // it returns that value, otherwise it returns the default value.
-  uint32_t GetMinorVersion() const;
+  // Compare |calculated_hash| with source hash in |operation|, return false and
+  // dump hash and set |error| if don't match.
+  // |source_fd| is the file descriptor of the source partition.
+  static bool ValidateSourceHash(const brillo::Blob& calculated_hash,
+                                 const InstallOperation& operation,
+                                 const FileDescriptorPtr source_fd,
+                                 ErrorCode* error);
 
   // Initialize partitions and allocate required space for an update with the
   // given |manifest|. |update_check_response_hash| is used to check if the
diff --git a/payload_consumer/delta_performer_integration_test.cc b/payload_consumer/delta_performer_integration_test.cc
index f2aeb03..74ddd27 100644
--- a/payload_consumer/delta_performer_integration_test.cc
+++ b/payload_consumer/delta_performer_integration_test.cc
@@ -52,7 +52,9 @@
 
 namespace chromeos_update_engine {
 
+using std::list;
 using std::string;
+using std::unique_ptr;
 using std::vector;
 using test_utils::GetBuildArtifactsPath;
 using test_utils::kRandomString;
@@ -76,22 +78,24 @@
 
 namespace {
 struct DeltaState {
-  string a_img;
-  string b_img;
-  string result_img;
+  unique_ptr<ScopedTempFile> a_img;
+  unique_ptr<ScopedTempFile> b_img;
+  unique_ptr<ScopedTempFile> result_img;
   size_t image_size;
 
-  string delta_path;
+  unique_ptr<ScopedTempFile> delta_file;
+  // The in-memory copy of delta file.
+  brillo::Blob delta;
   uint64_t metadata_size;
   uint32_t metadata_signature_size;
 
-  string old_kernel;
+  unique_ptr<ScopedTempFile> old_kernel;
   brillo::Blob old_kernel_data;
 
-  string new_kernel;
+  unique_ptr<ScopedTempFile> new_kernel;
   brillo::Blob new_kernel_data;
 
-  string result_kernel;
+  unique_ptr<ScopedTempFile> result_kernel;
   brillo::Blob result_kernel_data;
   size_t kernel_size;
 
@@ -99,9 +103,6 @@
   // the DeltaPerformer.
   InstallPlan install_plan;
 
-  // The in-memory copy of delta file.
-  brillo::Blob delta;
-
   // Mock and fake instances used by the delta performer.
   FakeBootControl fake_boot_control_;
   FakeHardware fake_hardware_;
@@ -155,7 +156,7 @@
     EXPECT_EQ(expected, performer.ValidateManifest());
   }
   void AddPartition(DeltaArchiveManifest* manifest,
-                    std::string name,
+                    string name,
                     int timestamp) {
     auto& partition = *manifest->add_partitions();
     partition.set_version(std::to_string(timestamp));
@@ -259,8 +260,7 @@
   }
   string signature_size_string = base::JoinString(signature_size_strings, ":");
 
-  test_utils::ScopedTempFile hash_file("hash.XXXXXX"),
-      metadata_hash_file("hash.XXXXXX");
+  ScopedTempFile hash_file("hash.XXXXXX"), metadata_hash_file("hash.XXXXXX");
   string delta_generator_path = GetBuildArtifactsPath("delta_generator");
   ASSERT_EQ(0,
             System(base::StringPrintf(
@@ -273,29 +273,27 @@
                 metadata_hash_file.path().c_str())));
 
   // Sign the hash with all private keys.
-  vector<test_utils::ScopedTempFile> sig_files, metadata_sig_files;
+  list<ScopedTempFile> sig_files, metadata_sig_files;
   vector<string> sig_file_paths, metadata_sig_file_paths;
   for (const auto& key_path : private_key_paths) {
     brillo::Blob hash, signature;
     ASSERT_TRUE(utils::ReadFile(hash_file.path(), &hash));
     ASSERT_TRUE(PayloadSigner::SignHash(hash, key_path, &signature));
 
-    test_utils::ScopedTempFile sig_file("signature.XXXXXX");
-    ASSERT_TRUE(test_utils::WriteFileVector(sig_file.path(), signature));
-    sig_file_paths.push_back(sig_file.path());
-    sig_files.push_back(std::move(sig_file));
+    sig_files.emplace_back("signature.XXXXXX");
+    ASSERT_TRUE(
+        test_utils::WriteFileVector(sig_files.back().path(), signature));
+    sig_file_paths.push_back(sig_files.back().path());
 
     brillo::Blob metadata_hash, metadata_signature;
     ASSERT_TRUE(utils::ReadFile(metadata_hash_file.path(), &metadata_hash));
     ASSERT_TRUE(
         PayloadSigner::SignHash(metadata_hash, key_path, &metadata_signature));
 
-    test_utils::ScopedTempFile metadata_sig_file("signature.XXXXXX");
-    ASSERT_TRUE(test_utils::WriteFileVector(metadata_sig_file.path(),
+    metadata_sig_files.emplace_back("metadata_signature.XXXXXX");
+    ASSERT_TRUE(test_utils::WriteFileVector(metadata_sig_files.back().path(),
                                             metadata_signature));
-
-    metadata_sig_file_paths.push_back(metadata_sig_file.path());
-    metadata_sig_files.push_back(std::move(metadata_sig_file));
+    metadata_sig_file_paths.push_back(metadata_sig_files.back().path());
   }
   string sig_files_string = base::JoinString(sig_file_paths, ":");
   string metadata_sig_files_string =
@@ -377,7 +375,7 @@
         GetBuildArtifactsPath(kUnittestPrivateKey2Path));
   }
 
-  std::string public_key;
+  string public_key;
   if (signature_test == kSignatureGeneratedShellRotateCl2) {
     public_key = GetBuildArtifactsPath(kUnittestPublicKey2Path);
   } else if (signature_test == kSignatureGeneratedShellECKey) {
@@ -397,44 +395,23 @@
                               SignatureTest signature_test,
                               DeltaState* state,
                               uint32_t minor_version) {
-  EXPECT_TRUE(utils::MakeTempFile("a_img.XXXXXX", &state->a_img, nullptr));
-  EXPECT_TRUE(utils::MakeTempFile("b_img.XXXXXX", &state->b_img, nullptr));
+  state->a_img.reset(new ScopedTempFile("a_img.XXXXXX"));
+  state->b_img.reset(new ScopedTempFile("b_img.XXXXXX"));
 
   // result_img is used in minor version 2. Instead of applying the update
   // in-place on A, we apply it to a new image, result_img.
-  EXPECT_TRUE(
-      utils::MakeTempFile("result_img.XXXXXX", &state->result_img, nullptr));
+  state->result_img.reset(new ScopedTempFile("result_img.XXXXXX"));
 
   EXPECT_TRUE(
       base::CopyFile(GetBuildArtifactsPath().Append("gen/disk_ext2_4k.img"),
-                     base::FilePath(state->a_img)));
+                     base::FilePath(state->a_img->path())));
 
-  state->image_size = utils::FileSize(state->a_img);
-
-  // Create ImageInfo A & B
-  ImageInfo old_image_info;
-  ImageInfo new_image_info;
-
-  if (!full_rootfs) {
-    old_image_info.set_channel("src-channel");
-    old_image_info.set_board("src-board");
-    old_image_info.set_version("src-version");
-    old_image_info.set_key("src-key");
-    old_image_info.set_build_channel("src-build-channel");
-    old_image_info.set_build_version("src-build-version");
-  }
-
-  new_image_info.set_channel("test-channel");
-  new_image_info.set_board("test-board");
-  new_image_info.set_version("test-version");
-  new_image_info.set_key("test-key");
-  new_image_info.set_build_channel("test-build-channel");
-  new_image_info.set_build_version("test-build-version");
+  state->image_size = utils::FileSize(state->a_img->path());
 
   // Make some changes to the A image.
   {
     string a_mnt;
-    ScopedLoopMounter b_mounter(state->a_img, &a_mnt, 0);
+    ScopedLoopMounter b_mounter(state->a_img->path(), &a_mnt, 0);
 
     brillo::Blob hardtocompress;
     while (hardtocompress.size() < 3 * kBlockSize) {
@@ -471,17 +448,18 @@
 
   // Create a result image with image_size bytes of garbage.
   brillo::Blob ones(state->image_size, 0xff);
-  EXPECT_TRUE(
-      utils::WriteFile(state->result_img.c_str(), ones.data(), ones.size()));
-  EXPECT_EQ(utils::FileSize(state->a_img), utils::FileSize(state->result_img));
+  EXPECT_TRUE(utils::WriteFile(
+      state->result_img->path().c_str(), ones.data(), ones.size()));
+  EXPECT_EQ(utils::FileSize(state->a_img->path()),
+            utils::FileSize(state->result_img->path()));
 
   EXPECT_TRUE(
       base::CopyFile(GetBuildArtifactsPath().Append("gen/disk_ext2_4k.img"),
-                     base::FilePath(state->b_img)));
+                     base::FilePath(state->b_img->path())));
   {
     // Make some changes to the B image.
     string b_mnt;
-    ScopedLoopMounter b_mounter(state->b_img, &b_mnt, 0);
+    ScopedLoopMounter b_mounter(state->b_img->path(), &b_mnt, 0);
     base::FilePath mnt_path(b_mnt);
 
     EXPECT_TRUE(base::CopyFile(mnt_path.Append("regular-small"),
@@ -529,18 +507,9 @@
         hardtocompress.size()));
   }
 
-  string old_kernel;
-  EXPECT_TRUE(
-      utils::MakeTempFile("old_kernel.XXXXXX", &state->old_kernel, nullptr));
-
-  string new_kernel;
-  EXPECT_TRUE(
-      utils::MakeTempFile("new_kernel.XXXXXX", &state->new_kernel, nullptr));
-
-  string result_kernel;
-  EXPECT_TRUE(utils::MakeTempFile(
-      "result_kernel.XXXXXX", &state->result_kernel, nullptr));
-
+  state->old_kernel.reset(new ScopedTempFile("old_kernel.XXXXXX"));
+  state->new_kernel.reset(new ScopedTempFile("new_kernel.XXXXXX"));
+  state->result_kernel.reset(new ScopedTempFile("result_kernel.XXXXXX"));
   state->kernel_size = kDefaultKernelSize;
   state->old_kernel_data.resize(kDefaultKernelSize);
   state->new_kernel_data.resize(state->old_kernel_data.size());
@@ -554,18 +523,17 @@
       std::begin(kNewData), std::end(kNewData), state->new_kernel_data.begin());
 
   // Write kernels to disk
-  EXPECT_TRUE(utils::WriteFile(state->old_kernel.c_str(),
+  EXPECT_TRUE(utils::WriteFile(state->old_kernel->path().c_str(),
                                state->old_kernel_data.data(),
                                state->old_kernel_data.size()));
-  EXPECT_TRUE(utils::WriteFile(state->new_kernel.c_str(),
+  EXPECT_TRUE(utils::WriteFile(state->new_kernel->path().c_str(),
                                state->new_kernel_data.data(),
                                state->new_kernel_data.size()));
-  EXPECT_TRUE(utils::WriteFile(state->result_kernel.c_str(),
+  EXPECT_TRUE(utils::WriteFile(state->result_kernel->path().c_str(),
                                state->result_kernel_data.data(),
                                state->result_kernel_data.size()));
 
-  EXPECT_TRUE(utils::MakeTempFile("delta.XXXXXX", &state->delta_path, nullptr));
-  LOG(INFO) << "delta path: " << state->delta_path;
+  state->delta_file.reset(new ScopedTempFile("delta.XXXXXX"));
   {
     const string private_key =
         signature_test == kSignatureGenerator
@@ -581,10 +549,10 @@
     if (!full_rootfs) {
       payload_config.source.partitions.emplace_back(kPartitionNameRoot);
       payload_config.source.partitions.emplace_back(kPartitionNameKernel);
-      payload_config.source.partitions.front().path = state->a_img;
+      payload_config.source.partitions.front().path = state->a_img->path();
       if (!full_kernel)
-        payload_config.source.partitions.back().path = state->old_kernel;
-      payload_config.source.image_info = old_image_info;
+        payload_config.source.partitions.back().path =
+            state->old_kernel->path();
       EXPECT_TRUE(payload_config.source.LoadImageSize());
       for (PartitionConfig& part : payload_config.source.partitions)
         EXPECT_TRUE(part.OpenFilesystem());
@@ -594,29 +562,30 @@
         payload_config.hard_chunk_size = 1024 * 1024;
     }
     payload_config.target.partitions.emplace_back(kPartitionNameRoot);
-    payload_config.target.partitions.back().path = state->b_img;
+    payload_config.target.partitions.back().path = state->b_img->path();
     payload_config.target.partitions.emplace_back(kPartitionNameKernel);
-    payload_config.target.partitions.back().path = state->new_kernel;
-    payload_config.target.image_info = new_image_info;
+    payload_config.target.partitions.back().path = state->new_kernel->path();
     EXPECT_TRUE(payload_config.target.LoadImageSize());
     for (PartitionConfig& part : payload_config.target.partitions)
       EXPECT_TRUE(part.OpenFilesystem());
 
     EXPECT_TRUE(payload_config.Validate());
-    EXPECT_TRUE(GenerateUpdatePayloadFile(
-        payload_config, state->delta_path, private_key, &state->metadata_size));
+    EXPECT_TRUE(GenerateUpdatePayloadFile(payload_config,
+                                          state->delta_file->path(),
+                                          private_key,
+                                          &state->metadata_size));
   }
   // Extend the "partitions" holding the file system a bit.
   EXPECT_EQ(0,
-            HANDLE_EINTR(truncate(state->a_img.c_str(),
+            HANDLE_EINTR(truncate(state->a_img->path().c_str(),
                                   state->image_size + 1024 * 1024)));
   EXPECT_EQ(static_cast<off_t>(state->image_size + 1024 * 1024),
-            utils::FileSize(state->a_img));
+            utils::FileSize(state->a_img->path()));
   EXPECT_EQ(0,
-            HANDLE_EINTR(truncate(state->b_img.c_str(),
+            HANDLE_EINTR(truncate(state->b_img->path().c_str(),
                                   state->image_size + 1024 * 1024)));
   EXPECT_EQ(static_cast<off_t>(state->image_size + 1024 * 1024),
-            utils::FileSize(state->b_img));
+            utils::FileSize(state->b_img->path()));
 
   if (signature_test == kSignatureGeneratedPlaceholder ||
       signature_test == kSignatureGeneratedPlaceholderMismatch) {
@@ -625,13 +594,13 @@
         GetBuildArtifactsPath(kUnittestPrivateKeyPath), &signature_size));
     LOG(INFO) << "Inserting placeholder signature.";
     ASSERT_TRUE(InsertSignaturePlaceholder(
-        signature_size, state->delta_path, &state->metadata_size));
+        signature_size, state->delta_file->path(), &state->metadata_size));
 
     if (signature_test == kSignatureGeneratedPlaceholderMismatch) {
       signature_size -= 1;
       LOG(INFO) << "Inserting mismatched placeholder signature.";
       ASSERT_FALSE(InsertSignaturePlaceholder(
-          signature_size, state->delta_path, &state->metadata_size));
+          signature_size, state->delta_file->path(), &state->metadata_size));
       return;
     }
   }
@@ -643,13 +612,13 @@
     // reflect the new size after adding the signature operation to the
     // manifest.
     LOG(INFO) << "Signing payload.";
-    SignGeneratedPayload(state->delta_path, &state->metadata_size);
+    SignGeneratedPayload(state->delta_file->path(), &state->metadata_size);
   } else if (signature_test == kSignatureGeneratedShell ||
              signature_test == kSignatureGeneratedShellECKey ||
              signature_test == kSignatureGeneratedShellBadKey ||
              signature_test == kSignatureGeneratedShellRotateCl1 ||
              signature_test == kSignatureGeneratedShellRotateCl2) {
-    SignGeneratedShellPayload(signature_test, state->delta_path);
+    SignGeneratedShellPayload(signature_test, state->delta_file->path());
   }
 }
 
@@ -663,7 +632,7 @@
                            uint32_t minor_version) {
   // Check the metadata.
   {
-    EXPECT_TRUE(utils::ReadFile(state->delta_path, &state->delta));
+    EXPECT_TRUE(utils::ReadFile(state->delta_file->path(), &state->delta));
     PayloadMetadata payload_metadata;
     EXPECT_TRUE(payload_metadata.ParsePayloadHeader(state->delta));
     state->metadata_size = payload_metadata.GetMetadataSize();
@@ -738,22 +707,6 @@
       EXPECT_FALSE(rootfs_part.old_partition_info().hash().empty());
     }
     EXPECT_FALSE(rootfs_part.new_partition_info().hash().empty());
-
-    EXPECT_EQ(manifest.new_image_info().channel(), "test-channel");
-    EXPECT_EQ(manifest.new_image_info().board(), "test-board");
-    EXPECT_EQ(manifest.new_image_info().version(), "test-version");
-    EXPECT_EQ(manifest.new_image_info().key(), "test-key");
-    EXPECT_EQ(manifest.new_image_info().build_channel(), "test-build-channel");
-    EXPECT_EQ(manifest.new_image_info().build_version(), "test-build-version");
-
-    if (!full_rootfs) {
-      EXPECT_EQ(manifest.old_image_info().channel(), "src-channel");
-      EXPECT_EQ(manifest.old_image_info().board(), "src-board");
-      EXPECT_EQ(manifest.old_image_info().version(), "src-version");
-      EXPECT_EQ(manifest.old_image_info().key(), "src-key");
-      EXPECT_EQ(manifest.old_image_info().build_channel(), "src-build-channel");
-      EXPECT_EQ(manifest.old_image_info().build_version(), "src-build-version");
-    }
   }
 
   MockPrefs prefs;
@@ -832,9 +785,10 @@
   (*performer)->set_public_key_path(public_key_path);
   (*performer)->set_update_certificates_path("");
 
-  EXPECT_EQ(static_cast<off_t>(state->image_size),
-            HashCalculator::RawHashOfFile(
-                state->a_img, state->image_size, &root_part.source_hash));
+  EXPECT_EQ(
+      static_cast<off_t>(state->image_size),
+      HashCalculator::RawHashOfFile(
+          state->a_img->path(), state->image_size, &root_part.source_hash));
   EXPECT_TRUE(HashCalculator::RawHashOfData(state->old_kernel_data,
                                             &kernel_part.source_hash));
 
@@ -842,13 +796,15 @@
   install_plan->partitions.clear();
 
   state->fake_boot_control_.SetPartitionDevice(
-      kPartitionNameRoot, install_plan->source_slot, state->a_img);
+      kPartitionNameRoot, install_plan->source_slot, state->a_img->path());
+  state->fake_boot_control_.SetPartitionDevice(kPartitionNameKernel,
+                                               install_plan->source_slot,
+                                               state->old_kernel->path());
   state->fake_boot_control_.SetPartitionDevice(
-      kPartitionNameKernel, install_plan->source_slot, state->old_kernel);
-  state->fake_boot_control_.SetPartitionDevice(
-      kPartitionNameRoot, install_plan->target_slot, state->result_img);
-  state->fake_boot_control_.SetPartitionDevice(
-      kPartitionNameKernel, install_plan->target_slot, state->result_kernel);
+      kPartitionNameRoot, install_plan->target_slot, state->result_img->path());
+  state->fake_boot_control_.SetPartitionDevice(kPartitionNameKernel,
+                                               install_plan->target_slot,
+                                               state->result_kernel->path());
 
   ErrorCode expected_error, actual_error;
   bool continue_writing;
@@ -927,12 +883,15 @@
     return;
   }
 
+  CompareFilesByBlock(state->result_kernel->path(),
+                      state->new_kernel->path(),
+                      state->kernel_size);
   CompareFilesByBlock(
-      state->result_kernel, state->new_kernel, state->kernel_size);
-  CompareFilesByBlock(state->result_img, state->b_img, state->image_size);
+      state->result_img->path(), state->b_img->path(), state->image_size);
 
   brillo::Blob updated_kernel_partition;
-  EXPECT_TRUE(utils::ReadFile(state->result_kernel, &updated_kernel_partition));
+  EXPECT_TRUE(
+      utils::ReadFile(state->result_kernel->path(), &updated_kernel_partition));
   ASSERT_GE(updated_kernel_partition.size(), base::size(kNewData));
   EXPECT_TRUE(std::equal(std::begin(kNewData),
                          std::end(kNewData),
@@ -951,9 +910,10 @@
 
   EXPECT_EQ(state->image_size, partitions[0].target_size);
   brillo::Blob expected_new_rootfs_hash;
-  EXPECT_EQ(static_cast<off_t>(state->image_size),
-            HashCalculator::RawHashOfFile(
-                state->b_img, state->image_size, &expected_new_rootfs_hash));
+  EXPECT_EQ(
+      static_cast<off_t>(state->image_size),
+      HashCalculator::RawHashOfFile(
+          state->b_img->path(), state->image_size, &expected_new_rootfs_hash));
   EXPECT_EQ(expected_new_rootfs_hash, partitions[0].target_hash);
 }
 
@@ -991,13 +951,6 @@
                     &state,
                     minor_version);
 
-  ScopedPathUnlinker a_img_unlinker(state.a_img);
-  ScopedPathUnlinker b_img_unlinker(state.b_img);
-  ScopedPathUnlinker new_img_unlinker(state.result_img);
-  ScopedPathUnlinker delta_unlinker(state.delta_path);
-  ScopedPathUnlinker old_kernel_unlinker(state.old_kernel);
-  ScopedPathUnlinker new_kernel_unlinker(state.new_kernel);
-  ScopedPathUnlinker result_kernel_unlinker(state.result_kernel);
   ApplyDeltaFile(full_kernel,
                  full_rootfs,
                  signature_test,
@@ -1015,11 +968,6 @@
   DeltaState state;
   uint64_t minor_version = kFullPayloadMinorVersion;
   GenerateDeltaFile(true, true, -1, kSignatureGenerated, &state, minor_version);
-  ScopedPathUnlinker a_img_unlinker(state.a_img);
-  ScopedPathUnlinker b_img_unlinker(state.b_img);
-  ScopedPathUnlinker delta_unlinker(state.delta_path);
-  ScopedPathUnlinker old_kernel_unlinker(state.old_kernel);
-  ScopedPathUnlinker new_kernel_unlinker(state.new_kernel);
   DeltaPerformer* performer = nullptr;
   ApplyDeltaFile(true,
                  true,
diff --git a/payload_consumer/delta_performer_unittest.cc b/payload_consumer/delta_performer_unittest.cc
index a5eb538..f742b1c 100644
--- a/payload_consumer/delta_performer_unittest.cc
+++ b/payload_consumer/delta_performer_unittest.cc
@@ -202,7 +202,7 @@
                                uint64_t major_version,
                                uint32_t minor_version,
                                PartitionConfig* old_part = nullptr) {
-    test_utils::ScopedTempFile blob_file("Blob-XXXXXX");
+    ScopedTempFile blob_file("Blob-XXXXXX");
     EXPECT_TRUE(test_utils::WriteFileVector(blob_file.path(), blob_data));
 
     PayloadGenerationConfig config;
@@ -236,7 +236,7 @@
     new_part.size = 0;
     payload.AddPartition(*old_part, new_part, {}, {});
 
-    test_utils::ScopedTempFile payload_file("Payload-XXXXXX");
+    ScopedTempFile payload_file("Payload-XXXXXX");
     string private_key =
         sign_payload ? GetBuildArtifactsPath(kUnittestPrivateKeyPath) : "";
     EXPECT_TRUE(payload.WritePayload(payload_file.path(),
@@ -287,7 +287,7 @@
                                   const string& source_path,
                                   const brillo::Blob& target_data,
                                   bool expect_success) {
-    test_utils::ScopedTempFile new_part("Partition-XXXXXX");
+    ScopedTempFile new_part("Partition-XXXXXX");
     EXPECT_TRUE(test_utils::WriteFileVector(new_part.path(), target_data));
 
     payload_.size = payload_data.size();
@@ -576,7 +576,7 @@
   EXPECT_TRUE(HashCalculator::RawHashOfData(expected_data, &src_hash));
   aop.op.set_src_sha256_hash(src_hash.data(), src_hash.size());
 
-  test_utils::ScopedTempFile source("Source-XXXXXX");
+  ScopedTempFile source("Source-XXXXXX");
   EXPECT_TRUE(test_utils::WriteFileVector(source.path(), expected_data));
 
   PartitionConfig old_part(kPartitionNameRoot);
@@ -604,7 +604,7 @@
   EXPECT_TRUE(HashCalculator::RawHashOfData(src, &src_hash));
   aop.op.set_src_sha256_hash(src_hash.data(), src_hash.size());
 
-  test_utils::ScopedTempFile source("Source-XXXXXX");
+  ScopedTempFile source("Source-XXXXXX");
   EXPECT_TRUE(test_utils::WriteFileVector(source.path(), src));
 
   PartitionConfig old_part(kPartitionNameRoot);
@@ -632,7 +632,7 @@
   EXPECT_TRUE(HashCalculator::RawHashOfData(expected_data, &src_hash));
   aop.op.set_src_sha256_hash(src_hash.data(), src_hash.size());
 
-  test_utils::ScopedTempFile source("Source-XXXXXX");
+  ScopedTempFile source("Source-XXXXXX");
   EXPECT_TRUE(test_utils::WriteFileVector(source.path(), actual_data));
 
   PartitionConfig old_part(kPartitionNameRoot);
@@ -645,9 +645,6 @@
   EXPECT_EQ(actual_data, ApplyPayload(payload_data, source.path(), false));
 }
 
-
-
-
 TEST_F(DeltaPerformerTest, ExtentsToByteStringTest) {
   uint64_t test[] = {1, 1, 4, 2, 0, 1};
   static_assert(base::size(test) % 2 == 0, "Array size uneven");
diff --git a/payload_consumer/download_action_unittest.cc b/payload_consumer/download_action_unittest.cc
index e6ca219..9daa791 100644
--- a/payload_consumer/download_action_unittest.cc
+++ b/payload_consumer/download_action_unittest.cc
@@ -51,7 +51,6 @@
 using base::WriteFile;
 using std::string;
 using std::unique_ptr;
-using test_utils::ScopedTempFile;
 using testing::_;
 using testing::AtLeast;
 using testing::InSequence;
@@ -133,7 +132,6 @@
   loop.SetAsCurrent();
   FakeSystemState fake_system_state;
 
-  // TODO(adlr): see if we need a different file for build bots
   ScopedTempFile output_temp_file;
   TestDirectFileWriter writer;
   EXPECT_EQ(
diff --git a/payload_consumer/extent_reader_unittest.cc b/payload_consumer/extent_reader_unittest.cc
index b7059bc..686f14d 100644
--- a/payload_consumer/extent_reader_unittest.cc
+++ b/payload_consumer/extent_reader_unittest.cc
@@ -72,7 +72,7 @@
   }
 
   FileDescriptorPtr fd_;
-  test_utils::ScopedTempFile temp_file_{"ExtentReaderTest-file.XXXXXX"};
+  ScopedTempFile temp_file_{"ExtentReaderTest-file.XXXXXX"};
   brillo::Blob sample_;
 };
 
diff --git a/payload_consumer/extent_writer_unittest.cc b/payload_consumer/extent_writer_unittest.cc
index aef856b..afebb1a 100644
--- a/payload_consumer/extent_writer_unittest.cc
+++ b/payload_consumer/extent_writer_unittest.cc
@@ -59,7 +59,7 @@
   void WriteAlignedExtents(size_t chunk_size, size_t first_chunk_size);
 
   FileDescriptorPtr fd_;
-  test_utils::ScopedTempFile temp_file_{"ExtentWriterTest-file.XXXXXX"};
+  ScopedTempFile temp_file_{"ExtentWriterTest-file.XXXXXX"};
 };
 
 TEST_F(ExtentWriterTest, SimpleTest) {
diff --git a/payload_consumer/file_descriptor_utils_unittest.cc b/payload_consumer/file_descriptor_utils_unittest.cc
index 48e610f..478893d 100644
--- a/payload_consumer/file_descriptor_utils_unittest.cc
+++ b/payload_consumer/file_descriptor_utils_unittest.cc
@@ -52,14 +52,13 @@
 class FileDescriptorUtilsTest : public ::testing::Test {
  protected:
   void SetUp() override {
-    EXPECT_TRUE(utils::MakeTempFile("fd_tgt.XXXXXX", &tgt_path_, nullptr));
-    EXPECT_TRUE(target_->Open(tgt_path_.c_str(), O_RDWR));
+    EXPECT_TRUE(target_->Open(tgt_file_.path().c_str(), O_RDWR));
   }
 
   // Check that the |target_| file contains |expected_contents|.
   void ExpectTarget(const std::string& expected_contents) {
     std::string target_contents;
-    EXPECT_TRUE(utils::ReadFile(tgt_path_, &target_contents));
+    EXPECT_TRUE(utils::ReadFile(tgt_file_.path(), &target_contents));
     EXPECT_EQ(expected_contents.size(), target_contents.size());
     if (target_contents != expected_contents) {
       ADD_FAILURE() << "Contents don't match.";
@@ -70,8 +69,7 @@
     }
   }
 
-  // Path to the target temporary file.
-  std::string tgt_path_;
+  ScopedTempFile tgt_file_{"fd_tgt.XXXXXX"};
 
   // Source and target file descriptor used for testing the tools.
   FakeFileDescriptor* fake_source_{new FakeFileDescriptor()};
diff --git a/payload_consumer/file_writer_unittest.cc b/payload_consumer/file_writer_unittest.cc
index 59cfe2b..3b959f3 100644
--- a/payload_consumer/file_writer_unittest.cc
+++ b/payload_consumer/file_writer_unittest.cc
@@ -35,8 +35,7 @@
 class FileWriterTest : public ::testing::Test {};
 
 TEST(FileWriterTest, SimpleTest) {
-  // Create a uniquely named file for testing.
-  test_utils::ScopedTempFile file("FileWriterTest-XXXXXX");
+  ScopedTempFile file("FileWriterTest-XXXXXX");
   DirectFileWriter file_writer;
   EXPECT_EQ(0,
             file_writer.Open(file.path().c_str(),
@@ -60,7 +59,7 @@
 
 TEST(FileWriterTest, WriteErrorTest) {
   // Create a uniquely named file for testing.
-  test_utils::ScopedTempFile file("FileWriterTest-XXXXXX");
+  ScopedTempFile file("FileWriterTest-XXXXXX");
   DirectFileWriter file_writer;
   EXPECT_EQ(0,
             file_writer.Open(file.path().c_str(),
diff --git a/payload_consumer/filesystem_verifier_action_unittest.cc b/payload_consumer/filesystem_verifier_action_unittest.cc
index 2971849..2c29b44 100644
--- a/payload_consumer/filesystem_verifier_action_unittest.cc
+++ b/payload_consumer/filesystem_verifier_action_unittest.cc
@@ -92,7 +92,7 @@
 
 bool FilesystemVerifierActionTest::DoTest(bool terminate_early,
                                           bool hash_fail) {
-  test_utils::ScopedTempFile a_loop_file("a_loop_file.XXXXXX");
+  ScopedTempFile a_loop_file("a_loop_file.XXXXXX");
 
   // Make random data for a.
   const size_t kLoopFileSize = 10 * 1024 * 1024 + 512;
@@ -278,7 +278,7 @@
 
 #ifdef __ANDROID__
 TEST_F(FilesystemVerifierActionTest, RunAsRootWriteVerityTest) {
-  test_utils::ScopedTempFile part_file("part_file.XXXXXX");
+  ScopedTempFile part_file("part_file.XXXXXX");
   constexpr size_t filesystem_size = 200 * 4096;
   constexpr size_t part_size = 256 * 4096;
   brillo::Blob part_data(filesystem_size, 0x1);
@@ -340,7 +340,7 @@
 #endif  // __ANDROID__
 
 TEST_F(FilesystemVerifierActionTest, RunAsRootSkipWriteVerityTest) {
-  test_utils::ScopedTempFile part_file("part_file.XXXXXX");
+  ScopedTempFile part_file("part_file.XXXXXX");
   constexpr size_t filesystem_size = 200 * 4096;
   constexpr size_t part_size = 256 * 4096;
   brillo::Blob part_data(part_size);
diff --git a/payload_consumer/install_plan.cc b/payload_consumer/install_plan.cc
index a313627..c7ef7b2 100644
--- a/payload_consumer/install_plan.cc
+++ b/payload_consumer/install_plan.cc
@@ -98,8 +98,8 @@
             << version_str
             << ", source_slot: " << BootControlInterface::SlotName(source_slot)
             << ", target_slot: " << BootControlInterface::SlotName(target_slot)
-            << ", initial url: " << url_str << payloads_str
-            << partitions_str << ", hash_checks_mandatory: "
+            << ", initial url: " << url_str << payloads_str << partitions_str
+            << ", hash_checks_mandatory: "
             << utils::ToString(hash_checks_mandatory)
             << ", powerwash_required: " << utils::ToString(powerwash_required)
             << ", switch_slot_on_reboot: "
diff --git a/payload_consumer/install_plan.h b/payload_consumer/install_plan.h
index f04c650..5534fb3 100644
--- a/payload_consumer/install_plan.h
+++ b/payload_consumer/install_plan.h
@@ -59,8 +59,8 @@
 
   struct Payload {
     std::vector<std::string> payload_urls;  // URLs to download the payload
-    uint64_t size = 0;               // size of the payload
-    uint64_t metadata_size = 0;      // size of the metadata
+    uint64_t size = 0;                      // size of the payload
+    uint64_t metadata_size = 0;             // size of the metadata
     std::string metadata_signature;  // signature of the metadata in base64
     brillo::Blob hash;               // SHA256 hash of the payload
     InstallPayloadType type{InstallPayloadType::kUnknown};
diff --git a/payload_consumer/partition_update_generator_android.cc b/payload_consumer/partition_update_generator_android.cc
index d5d5313..25771e1 100644
--- a/payload_consumer/partition_update_generator_android.cc
+++ b/payload_consumer/partition_update_generator_android.cc
@@ -32,10 +32,8 @@
 namespace chromeos_update_engine {
 
 PartitionUpdateGeneratorAndroid::PartitionUpdateGeneratorAndroid(
-    BootControlInterface* boot_control,
-    size_t block_size)
-    : boot_control_(boot_control),
-      block_size_(block_size) {}
+    BootControlInterface* boot_control, size_t block_size)
+    : boot_control_(boot_control), block_size_(block_size) {}
 
 bool PartitionUpdateGeneratorAndroid::
     GenerateOperationsForPartitionsNotInPayload(
diff --git a/payload_consumer/partition_writer_unittest.cc b/payload_consumer/partition_writer_unittest.cc
index c1ff4f4..1ef4783 100644
--- a/payload_consumer/partition_writer_unittest.cc
+++ b/payload_consumer/partition_writer_unittest.cc
@@ -81,14 +81,14 @@
 
   brillo::Blob PerformSourceCopyOp(const InstallOperation& op,
                                    const brillo::Blob blob_data) {
-    test_utils::ScopedTempFile source_partition("Blob-XXXXXX");
+    ScopedTempFile source_partition("Blob-XXXXXX");
     DirectExtentWriter extent_writer;
     FileDescriptorPtr fd(new EintrSafeFileDescriptor());
     EXPECT_TRUE(fd->Open(source_partition.path().c_str(), O_RDWR));
     EXPECT_TRUE(extent_writer.Init(fd, op.src_extents(), kBlockSize));
     EXPECT_TRUE(extent_writer.Write(blob_data.data(), blob_data.size()));
 
-    test_utils::ScopedTempFile target_partition("Blob-XXXXXX");
+    ScopedTempFile target_partition("Blob-XXXXXX");
 
     install_part_.source_path = source_partition.path();
     install_part_.target_path = target_partition.path();
@@ -120,7 +120,7 @@
 // file descriptor when the size of the error corrected one is too small.
 TEST_F(PartitionWriterTest, ErrorCorrectionSourceCopyWhenNoHashFallbackTest) {
   constexpr size_t kCopyOperationSize = 4 * 4096;
-  test_utils::ScopedTempFile source("Source-XXXXXX");
+  ScopedTempFile source("Source-XXXXXX");
   // Setup the source path with the right expected data.
   brillo::Blob expected_data = FakeFileDescriptorData(kCopyOperationSize);
   EXPECT_TRUE(test_utils::WriteFileVector(source.path(), expected_data));
@@ -172,7 +172,7 @@
 
 TEST_F(PartitionWriterTest, ChooseSourceFDTest) {
   constexpr size_t kSourceSize = 4 * 4096;
-  test_utils::ScopedTempFile source("Source-XXXXXX");
+  ScopedTempFile source("Source-XXXXXX");
   // Write invalid data to the source image, which doesn't match the expected
   // hash.
   brillo::Blob invalid_data(kSourceSize, 0x55);
diff --git a/payload_consumer/payload_verifier.cc b/payload_consumer/payload_verifier.cc
index 24e337e..85902c8 100644
--- a/payload_consumer/payload_verifier.cc
+++ b/payload_consumer/payload_verifier.cc
@@ -203,7 +203,7 @@
   //
   // openssl rsautl -verify -pubin -inkey <(echo pem_public_key)
   //   -in |sig_data| -out |out_hash_data|
-  RSA* rsa = EVP_PKEY_get0_RSA(public_key);
+  RSA* rsa = EVP_PKEY_get0_RSA(const_cast<EVP_PKEY*>(public_key));
 
   TEST_AND_RETURN_FALSE(rsa != nullptr);
   unsigned int keysize = RSA_size(rsa);
diff --git a/payload_consumer/postinstall_runner_action.cc b/payload_consumer/postinstall_runner_action.cc
index e8fa81b..91c3a64 100644
--- a/payload_consumer/postinstall_runner_action.cc
+++ b/payload_consumer/postinstall_runner_action.cc
@@ -63,9 +63,8 @@
   // that retains a small amount of system state such as enrollment and
   // network configuration. In both cases all user accounts are deleted.
   if (install_plan_.powerwash_required || install_plan_.is_rollback) {
-    bool save_rollback_data =
-        install_plan_.is_rollback && install_plan_.rollback_data_save_requested;
-    if (hardware_->SchedulePowerwash(save_rollback_data)) {
+    if (hardware_->SchedulePowerwash(
+            install_plan_.rollback_data_save_requested)) {
       powerwash_scheduled_ = true;
     } else {
       return CompletePostinstall(ErrorCode::kPostinstallPowerwashError);
diff --git a/payload_consumer/postinstall_runner_action_unittest.cc b/payload_consumer/postinstall_runner_action_unittest.cc
index cf5158b..5910c23 100644
--- a/payload_consumer/postinstall_runner_action_unittest.cc
+++ b/payload_consumer/postinstall_runner_action_unittest.cc
@@ -26,9 +26,14 @@
 
 #include <base/bind.h>
 #include <base/files/file_util.h>
+#if BASE_VER < 780000  // Android
 #include <base/message_loop/message_loop.h>
+#endif  // BASE_VER < 780000
 #include <base/strings/string_util.h>
 #include <base/strings/stringprintf.h>
+#if BASE_VER >= 780000  // CrOS
+#include <base/task/single_thread_task_executor.h>
+#endif  // BASE_VER >= 780000
 #include <brillo/message_loops/base_message_loop.h>
 #include <brillo/message_loops/message_loop_utils.h>
 #include <gmock/gmock.h>
@@ -156,8 +161,13 @@
   }
 
  protected:
+#if BASE_VER < 780000  // Android
   base::MessageLoopForIO base_loop_;
   brillo::BaseMessageLoop loop_{&base_loop_};
+#else   // CrOS
+  base::SingleThreadTaskExecutor base_loop_{base::MessagePumpType::IO};
+  brillo::BaseMessageLoop loop_{base_loop_.task_runner()};
+#endif  // BASE_VER < 780000
   brillo::AsynchronousSignalHandler async_signal_handler_;
   Subprocess subprocess_;
 
diff --git a/payload_consumer/verity_writer_android_unittest.cc b/payload_consumer/verity_writer_android_unittest.cc
index f943ce8..ec22ffb 100644
--- a/payload_consumer/verity_writer_android_unittest.cc
+++ b/payload_consumer/verity_writer_android_unittest.cc
@@ -39,7 +39,7 @@
 
   VerityWriterAndroid verity_writer_;
   InstallPlan::Partition partition_;
-  test_utils::ScopedTempFile temp_file_;
+  ScopedTempFile temp_file_;
 };
 
 TEST_F(VerityWriterAndroidTest, SimpleTest) {
diff --git a/payload_generator/ab_generator_unittest.cc b/payload_generator/ab_generator_unittest.cc
index 7a95284..84eeb77 100644
--- a/payload_generator/ab_generator_unittest.cc
+++ b/payload_generator/ab_generator_unittest.cc
@@ -70,8 +70,7 @@
       part_data.push_back(dis(gen));
   }
   ASSERT_EQ(part_size, part_data.size());
-  test_utils::ScopedTempFile part_file(
-      "SplitReplaceOrReplaceXzTest_part.XXXXXX");
+  ScopedTempFile part_file("SplitReplaceOrReplaceXzTest_part.XXXXXX");
   ASSERT_TRUE(test_utils::WriteFileVector(part_file.path(), part_data));
 
   // Create original operation and blob data.
@@ -107,8 +106,7 @@
   aop.name = "SplitTestOp";
 
   // Create the data file.
-  test_utils::ScopedTempFile data_file(
-      "SplitReplaceOrReplaceXzTest_data.XXXXXX");
+  ScopedTempFile data_file("SplitReplaceOrReplaceXzTest_data.XXXXXX");
   EXPECT_TRUE(test_utils::WriteFileVector(data_file.path(), op_blob));
   int data_fd = open(data_file.path().c_str(), O_RDWR, 000);
   EXPECT_GE(data_fd, 0);
@@ -220,8 +218,7 @@
       part_data.push_back(dis(gen));
   }
   ASSERT_EQ(part_size, part_data.size());
-  test_utils::ScopedTempFile part_file(
-      "MergeReplaceOrReplaceXzTest_part.XXXXXX");
+  ScopedTempFile part_file("MergeReplaceOrReplaceXzTest_part.XXXXXX");
   ASSERT_TRUE(test_utils::WriteFileVector(part_file.path(), part_data));
 
   // Create original operations and blob data.
@@ -271,8 +268,7 @@
   aops.push_back(second_aop);
 
   // Create the data file.
-  test_utils::ScopedTempFile data_file(
-      "MergeReplaceOrReplaceXzTest_data.XXXXXX");
+  ScopedTempFile data_file("MergeReplaceOrReplaceXzTest_data.XXXXXX");
   EXPECT_TRUE(test_utils::WriteFileVector(data_file.path(), blob_data));
   int data_fd = open(data_file.path().c_str(), O_RDWR, 000);
   EXPECT_GE(data_fd, 0);
@@ -561,7 +557,7 @@
   second_aop.op = second_op;
   aops.push_back(second_aop);
 
-  test_utils::ScopedTempFile src_part_file("AddSourceHashTest_src_part.XXXXXX");
+  ScopedTempFile src_part_file("AddSourceHashTest_src_part.XXXXXX");
   brillo::Blob src_data(kBlockSize);
   test_utils::FillWithData(&src_data);
   ASSERT_TRUE(test_utils::WriteFileVector(src_part_file.path(), src_data));
diff --git a/payload_generator/blob_file_writer_unittest.cc b/payload_generator/blob_file_writer_unittest.cc
index 487bc73..f4dcafb 100644
--- a/payload_generator/blob_file_writer_unittest.cc
+++ b/payload_generator/blob_file_writer_unittest.cc
@@ -31,24 +31,21 @@
 class BlobFileWriterTest : public ::testing::Test {};
 
 TEST(BlobFileWriterTest, SimpleTest) {
-  string blob_path;
-  int blob_fd;
-  EXPECT_TRUE(
-      utils::MakeTempFile("BlobFileWriterTest.XXXXXX", &blob_path, &blob_fd));
+  ScopedTempFile blob_file("BlobFileWriterTest.XXXXXX", true);
   off_t blob_file_size = 0;
-  BlobFileWriter blob_file(blob_fd, &blob_file_size);
+  BlobFileWriter blob_file_writer(blob_file.fd(), &blob_file_size);
 
-  off_t blob_size = 1024;
-  brillo::Blob blob(blob_size);
+  const off_t kBlobSize = 1024;
+  brillo::Blob blob(kBlobSize);
   FillWithData(&blob);
-  EXPECT_EQ(0, blob_file.StoreBlob(blob));
-  EXPECT_EQ(blob_size, blob_file.StoreBlob(blob));
+  EXPECT_EQ(0, blob_file_writer.StoreBlob(blob));
+  EXPECT_EQ(kBlobSize, blob_file_writer.StoreBlob(blob));
 
-  brillo::Blob stored_blob(blob_size);
+  brillo::Blob stored_blob(kBlobSize);
   ssize_t bytes_read;
-  ASSERT_TRUE(
-      utils::PReadAll(blob_fd, stored_blob.data(), blob_size, 0, &bytes_read));
-  EXPECT_EQ(bytes_read, blob_size);
+  ASSERT_TRUE(utils::PReadAll(
+      blob_file.fd(), stored_blob.data(), kBlobSize, 0, &bytes_read));
+  EXPECT_EQ(bytes_read, kBlobSize);
   EXPECT_EQ(blob, stored_blob);
 }
 
diff --git a/payload_generator/block_mapping_unittest.cc b/payload_generator/block_mapping_unittest.cc
index 9b9b4f1..017548a 100644
--- a/payload_generator/block_mapping_unittest.cc
+++ b/payload_generator/block_mapping_unittest.cc
@@ -36,8 +36,8 @@
 class BlockMappingTest : public ::testing::Test {
  protected:
   // Old new partition files used in testing.
-  test_utils::ScopedTempFile old_part_{"BlockMappingTest_old.XXXXXX"};
-  test_utils::ScopedTempFile new_part_{"BlockMappingTest_new.XXXXXX"};
+  ScopedTempFile old_part_{"BlockMappingTest_old.XXXXXX"};
+  ScopedTempFile new_part_{"BlockMappingTest_new.XXXXXX"};
 
   size_t block_size_{1024};
   BlockMapping bm_{block_size_};  // BlockMapping under test.
diff --git a/payload_generator/boot_img_filesystem_unittest.cc b/payload_generator/boot_img_filesystem_unittest.cc
index 0b115e0..7805156 100644
--- a/payload_generator/boot_img_filesystem_unittest.cc
+++ b/payload_generator/boot_img_filesystem_unittest.cc
@@ -63,7 +63,7 @@
     return boot_img;
   }
 
-  test_utils::ScopedTempFile boot_file_;
+  ScopedTempFile boot_file_;
 };
 
 TEST_F(BootImgFilesystemTest, SimpleTest) {
diff --git a/payload_generator/delta_diff_generator.cc b/payload_generator/delta_diff_generator.cc
index c2b35ee..ff8b0da 100644
--- a/payload_generator/delta_diff_generator.cc
+++ b/payload_generator/delta_diff_generator.cc
@@ -119,18 +119,10 @@
   PayloadFile payload;
   TEST_AND_RETURN_FALSE(payload.Init(config));
 
-  const string kTempFileTemplate("CrAU_temp_data.XXXXXX");
-  string temp_file_path;
-  int data_file_fd;
-  TEST_AND_RETURN_FALSE(
-      utils::MakeTempFile(kTempFileTemplate, &temp_file_path, &data_file_fd));
-  ScopedPathUnlinker temp_file_unlinker(temp_file_path);
-  TEST_AND_RETURN_FALSE(data_file_fd >= 0);
-
+  ScopedTempFile data_file("CrAU_temp_data.XXXXXX", true);
   {
     off_t data_file_size = 0;
-    ScopedFdCloser data_file_fd_closer(&data_file_fd);
-    BlobFileWriter blob_file(data_file_fd, &data_file_size);
+    BlobFileWriter blob_file(data_file.fd(), &data_file_size);
     if (config.is_delta) {
       TEST_AND_RETURN_FALSE(config.source.partitions.size() ==
                             config.target.partitions.size());
@@ -190,11 +182,12 @@
                                std::move(all_merge_sequences[i])));
     }
   }
+  data_file.CloseFd();
 
   LOG(INFO) << "Writing payload file...";
   // Write payload file to disk.
   TEST_AND_RETURN_FALSE(payload.WritePayload(
-      output_path, temp_file_path, private_key_path, metadata_size));
+      output_path, data_file.path(), private_key_path, metadata_size));
 
   LOG(INFO) << "All done. Successfully created delta file with "
             << "metadata size = " << *metadata_size;
diff --git a/payload_generator/delta_diff_utils.cc b/payload_generator/delta_diff_utils.cc
index 220c7ae..3c025e1 100644
--- a/payload_generator/delta_diff_utils.cc
+++ b/payload_generator/delta_diff_utils.cc
@@ -822,17 +822,13 @@
         // Only Puffdiff if both files have at least one deflate left.
         if (!src_deflates.empty() && !dst_deflates.empty()) {
           brillo::Blob puffdiff_delta;
-          string temp_file_path;
-          TEST_AND_RETURN_FALSE(utils::MakeTempFile(
-              "puffdiff-delta.XXXXXX", &temp_file_path, nullptr));
-          ScopedPathUnlinker temp_file_unlinker(temp_file_path);
-
+          ScopedTempFile temp_file("puffdiff-delta.XXXXXX");
           // Perform PuffDiff operation.
           TEST_AND_RETURN_FALSE(puffin::PuffDiff(old_data,
                                                  new_data,
                                                  src_deflates,
                                                  dst_deflates,
-                                                 temp_file_path,
+                                                 temp_file.path(),
                                                  &puffdiff_delta));
           TEST_AND_RETURN_FALSE(puffdiff_delta.size() > 0);
           if (IsDiffOperationBetter(operation,
diff --git a/payload_generator/delta_diff_utils_unittest.cc b/payload_generator/delta_diff_utils_unittest.cc
index 0857f9c..f2db1bd 100644
--- a/payload_generator/delta_diff_utils_unittest.cc
+++ b/payload_generator/delta_diff_utils_unittest.cc
@@ -69,13 +69,12 @@
 // Create a fake filesystem of the given |size| and initialize the partition
 // holding it in the PartitionConfig |part|.
 void CreatePartition(PartitionConfig* part,
-                     const string& pattern,
+                     ScopedTempFile* part_file,
                      uint64_t block_size,
                      off_t size) {
-  int fd = -1;
-  ASSERT_TRUE(utils::MakeTempFile(pattern.c_str(), &part->path, &fd));
-  ASSERT_EQ(0, ftruncate(fd, size));
-  ASSERT_EQ(0, close(fd));
+  part->path = part_file->path();
+  ASSERT_EQ(0, ftruncate(part_file->fd(), size));
+  part_file->CloseFd();
   part->fs_interface.reset(new FakeFilesystem(block_size, size / block_size));
   part->size = size;
 }
@@ -112,30 +111,20 @@
 
   void SetUp() override {
     CreatePartition(&old_part_,
-                    "DeltaDiffUtilsTest-old_part-XXXXXX",
+                    &old_part_file_,
                     block_size_,
                     block_size_ * kDefaultBlockCount);
     CreatePartition(&new_part_,
-                    "DeltaDiffUtilsTest-old_part-XXXXXX",
+                    &new_part_file_,
                     block_size_,
                     block_size_ * kDefaultBlockCount);
-    ASSERT_TRUE(utils::MakeTempFile(
-        "DeltaDiffUtilsTest-blob-XXXXXX", &blob_path_, &blob_fd_));
-  }
-
-  void TearDown() override {
-    unlink(old_part_.path.c_str());
-    unlink(new_part_.path.c_str());
-    if (blob_fd_ != -1)
-      close(blob_fd_);
-    unlink(blob_path_.c_str());
   }
 
   // Helper function to call DeltaMovedAndZeroBlocks() using this class' data
   // members. This simply avoids repeating all the arguments that never change.
   bool RunDeltaMovedAndZeroBlocks(ssize_t chunk_blocks,
                                   uint32_t minor_version) {
-    BlobFileWriter blob_file(blob_fd_, &blob_size_);
+    BlobFileWriter blob_file(tmp_blob_file_.fd(), &blob_size_);
     PayloadVersion version(kBrilloMajorPayloadVersion, minor_version);
     ExtentRanges old_zero_blocks;
     return diff_utils::DeltaMovedAndZeroBlocks(&aops_,
@@ -155,10 +144,11 @@
   // with
   PartitionConfig old_part_{"part"};
   PartitionConfig new_part_{"part"};
+  ScopedTempFile old_part_file_{"DeltaDiffUtilsTest-old_part-XXXXXX", true};
+  ScopedTempFile new_part_file_{"DeltaDiffUtilsTest-new_part-XXXXXX", true};
 
   // The file holding the output blob from the various diff utils functions.
-  string blob_path_;
-  int blob_fd_{-1};
+  ScopedTempFile tmp_blob_file_{"DeltaDiffUtilsTest-blob-XXXXXX", true};
   off_t blob_size_{0};
 
   size_t block_size_{kBlockSize};
@@ -173,7 +163,7 @@
   new_part_.verity.hash_tree_extent = ExtentForRange(20, 30);
   new_part_.verity.fec_extent = ExtentForRange(40, 50);
 
-  BlobFileWriter blob_file(blob_fd_, &blob_size_);
+  BlobFileWriter blob_file(tmp_blob_file_.fd(), &blob_size_);
   EXPECT_TRUE(diff_utils::DeltaReadPartition(
       &aops_,
       old_part_,
diff --git a/payload_generator/ext2_filesystem_unittest.cc b/payload_generator/ext2_filesystem_unittest.cc
index 54600e9..88e1538 100644
--- a/payload_generator/ext2_filesystem_unittest.cc
+++ b/payload_generator/ext2_filesystem_unittest.cc
@@ -62,7 +62,7 @@
 class Ext2FilesystemTest : public ::testing::Test {};
 
 TEST_F(Ext2FilesystemTest, InvalidFilesystem) {
-  test_utils::ScopedTempFile fs_filename_{"Ext2FilesystemTest-XXXXXX"};
+  ScopedTempFile fs_filename_{"Ext2FilesystemTest-XXXXXX"};
   ASSERT_EQ(0, truncate(fs_filename_.path().c_str(), kDefaultFilesystemSize));
   unique_ptr<Ext2Filesystem> fs =
       Ext2Filesystem::CreateFromFile(fs_filename_.path());
diff --git a/payload_generator/extent_ranges_unittest.cc b/payload_generator/extent_ranges_unittest.cc
index 326e936..f55bb73 100644
--- a/payload_generator/extent_ranges_unittest.cc
+++ b/payload_generator/extent_ranges_unittest.cc
@@ -52,8 +52,8 @@
   }
 }
 
-#define EXPECT_RANGE_EQ(ranges, var)                      \
-  do {                                                    \
+#define EXPECT_RANGE_EQ(ranges, var)                       \
+  do {                                                     \
     ExpectRangeEq(ranges, var, base::size(var), __LINE__); \
   } while (0)
 
diff --git a/payload_generator/full_update_generator_unittest.cc b/payload_generator/full_update_generator_unittest.cc
index 5f39e8b..d3b3491 100644
--- a/payload_generator/full_update_generator_unittest.cc
+++ b/payload_generator/full_update_generator_unittest.cc
@@ -41,11 +41,9 @@
     config_.block_size = 4096;
 
     new_part_conf.path = part_file_.path();
-    EXPECT_TRUE(utils::MakeTempFile(
-        "FullUpdateTest_blobs.XXXXXX", &out_blobs_path_, &out_blobs_fd_));
 
-    blob_file_.reset(new BlobFileWriter(out_blobs_fd_, &out_blobs_length_));
-    out_blobs_unlinker_.reset(new ScopedPathUnlinker(out_blobs_path_));
+    blob_file_writer_.reset(
+        new BlobFileWriter(blob_file_.fd(), &out_blobs_length_));
   }
 
   PayloadGenerationConfig config_;
@@ -54,14 +52,11 @@
   vector<AnnotatedOperation> aops;
 
   // Output file holding the payload blobs.
-  string out_blobs_path_;
-  int out_blobs_fd_{-1};
   off_t out_blobs_length_{0};
-  ScopedFdCloser out_blobs_fd_closer_{&out_blobs_fd_};
-  test_utils::ScopedTempFile part_file_{"FullUpdateTest_partition.XXXXXX"};
+  ScopedTempFile part_file_{"FullUpdateTest_partition.XXXXXX"};
 
-  std::unique_ptr<BlobFileWriter> blob_file_;
-  std::unique_ptr<ScopedPathUnlinker> out_blobs_unlinker_;
+  ScopedTempFile blob_file_{"FullUpdateTest_blobs.XXXXXX", true};
+  std::unique_ptr<BlobFileWriter> blob_file_writer_;
 
   // FullUpdateGenerator under test.
   FullUpdateGenerator generator_;
@@ -77,7 +72,7 @@
   EXPECT_TRUE(generator_.GenerateOperations(config_,
                                             new_part_conf,  // this is ignored
                                             new_part_conf,
-                                            blob_file_.get(),
+                                            blob_file_writer_.get(),
                                             &aops));
   int64_t new_part_chunks = new_part_conf.size / config_.hard_chunk_size;
   EXPECT_EQ(new_part_chunks, static_cast<int64_t>(aops.size()));
@@ -108,7 +103,7 @@
   EXPECT_TRUE(generator_.GenerateOperations(config_,
                                             new_part_conf,  // this is ignored
                                             new_part_conf,
-                                            blob_file_.get(),
+                                            blob_file_writer_.get(),
                                             &aops));
   // new_part has one chunk and a half.
   EXPECT_EQ(2U, aops.size());
@@ -129,7 +124,7 @@
   EXPECT_TRUE(generator_.GenerateOperations(config_,
                                             new_part_conf,  // this is ignored
                                             new_part_conf,
-                                            blob_file_.get(),
+                                            blob_file_writer_.get(),
                                             &aops));
 
   // new_part has less than one chunk.
diff --git a/payload_generator/generate_delta_main.cc b/payload_generator/generate_delta_main.cc
index dd41a29..1944847 100644
--- a/payload_generator/generate_delta_main.cc
+++ b/payload_generator/generate_delta_main.cc
@@ -74,38 +74,6 @@
   }
 }
 
-bool ParseImageInfo(const string& channel,
-                    const string& board,
-                    const string& version,
-                    const string& key,
-                    const string& build_channel,
-                    const string& build_version,
-                    ImageInfo* image_info) {
-  // All of these arguments should be present or missing.
-  bool empty = channel.empty();
-
-  CHECK_EQ(channel.empty(), empty);
-  CHECK_EQ(board.empty(), empty);
-  CHECK_EQ(version.empty(), empty);
-  CHECK_EQ(key.empty(), empty);
-
-  if (empty)
-    return false;
-
-  image_info->set_channel(channel);
-  image_info->set_board(board);
-  image_info->set_version(version);
-  image_info->set_key(key);
-
-  image_info->set_build_channel(build_channel.empty() ? channel
-                                                      : build_channel);
-
-  image_info->set_build_version(build_version.empty() ? version
-                                                      : build_version);
-
-  return true;
-}
-
 void CalculateHashForSigning(const vector<size_t>& sizes,
                              const string& out_hash_file,
                              const string& out_metadata_hash_file,
@@ -426,51 +394,6 @@
       "The per-partition maximum timestamps which the OS allowed to apply this "
       "payload. Passed in comma separated pairs, e.x. system:1234,vendor:5678");
 
-  DEFINE_string(old_channel,
-                "",
-                "The channel for the old image. 'dev-channel', 'npo-channel', "
-                "etc. Ignored, except during delta generation.");
-  DEFINE_string(old_board,
-                "",
-                "The board for the old image. 'x86-mario', 'lumpy', "
-                "etc. Ignored, except during delta generation.");
-  DEFINE_string(
-      old_version, "", "The build version of the old image. 1.2.3, etc.");
-  DEFINE_string(old_key,
-                "",
-                "The key used to sign the old image. 'premp', 'mp', 'mp-v3',"
-                " etc");
-  DEFINE_string(old_build_channel,
-                "",
-                "The channel for the build of the old image. 'dev-channel', "
-                "etc, but will never contain special channels such as "
-                "'npo-channel'. Ignored, except during delta generation.");
-  DEFINE_string(old_build_version,
-                "",
-                "The version of the build containing the old image.");
-
-  DEFINE_string(new_channel,
-                "",
-                "The channel for the new image. 'dev-channel', 'npo-channel', "
-                "etc. Ignored, except during delta generation.");
-  DEFINE_string(new_board,
-                "",
-                "The board for the new image. 'x86-mario', 'lumpy', "
-                "etc. Ignored, except during delta generation.");
-  DEFINE_string(
-      new_version, "", "The build version of the new image. 1.2.3, etc.");
-  DEFINE_string(new_key,
-                "",
-                "The key used to sign the new image. 'premp', 'mp', 'mp-v3',"
-                " etc");
-  DEFINE_string(new_build_channel,
-                "",
-                "The channel for the build of the new image. 'dev-channel', "
-                "etc, but will never contain special channels such as "
-                "'npo-channel'. Ignored, except during delta generation.");
-  DEFINE_string(new_build_version,
-                "",
-                "The version of the build containing the new image.");
   DEFINE_string(new_postinstall_config_file,
                 "",
                 "A config file specifying postinstall related metadata. "
@@ -501,7 +424,11 @@
   Terminator::Init();
 
   logging::LoggingSettings log_settings;
+#if BASE_VER < 780000
   log_settings.log_file = "delta_generator.log";
+#else
+  log_settings.log_file_path = "delta_generator.log";
+#endif
   log_settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG;
   log_settings.lock_log = logging::LOCK_LOG_FILE;
   log_settings.delete_old = logging::APPEND_TO_OLD_LOG_FILE;
@@ -680,24 +607,6 @@
 
   CHECK(!FLAGS_out_file.empty());
 
-  // Ignore failures. These are optional arguments.
-  ParseImageInfo(FLAGS_new_channel,
-                 FLAGS_new_board,
-                 FLAGS_new_version,
-                 FLAGS_new_key,
-                 FLAGS_new_build_channel,
-                 FLAGS_new_build_version,
-                 &payload_config.target.image_info);
-
-  // Ignore failures. These are optional arguments.
-  ParseImageInfo(FLAGS_old_channel,
-                 FLAGS_old_board,
-                 FLAGS_old_version,
-                 FLAGS_old_key,
-                 FLAGS_old_build_channel,
-                 FLAGS_old_build_version,
-                 &payload_config.source.image_info);
-
   payload_config.rootfs_partition_size = FLAGS_rootfs_partition_size;
 
   if (payload_config.is_delta) {
diff --git a/payload_generator/mapfile_filesystem_unittest.cc b/payload_generator/mapfile_filesystem_unittest.cc
index 36ae3bf..57b672b 100644
--- a/payload_generator/mapfile_filesystem_unittest.cc
+++ b/payload_generator/mapfile_filesystem_unittest.cc
@@ -55,8 +55,8 @@
 
 class MapfileFilesystemTest : public ::testing::Test {
  protected:
-  test_utils::ScopedTempFile temp_file_{"mapfile_file.XXXXXX"};
-  test_utils::ScopedTempFile temp_mapfile_{"mapfile_mapfile.XXXXXX"};
+  ScopedTempFile temp_file_{"mapfile_file.XXXXXX"};
+  ScopedTempFile temp_mapfile_{"mapfile_mapfile.XXXXXX"};
 };
 
 TEST_F(MapfileFilesystemTest, EmptyFilesystem) {
diff --git a/payload_generator/payload_file.cc b/payload_generator/payload_file.cc
index 49dff4e..74423d1 100644
--- a/payload_generator/payload_file.cc
+++ b/payload_generator/payload_file.cc
@@ -64,13 +64,6 @@
   TEST_AND_RETURN_FALSE(config.version.Validate());
   major_version_ = config.version.major;
   manifest_.set_minor_version(config.version.minor);
-
-  if (!config.source.ImageInfoIsEmpty())
-    *(manifest_.mutable_old_image_info()) = config.source.image_info;
-
-  if (!config.target.ImageInfoIsEmpty())
-    *(manifest_.mutable_new_image_info()) = config.target.image_info;
-
   manifest_.set_block_size(config.block_size);
   manifest_.set_max_timestamp(config.max_timestamp);
 
@@ -110,11 +103,9 @@
                                const string& private_key_path,
                                uint64_t* metadata_size_out) {
   // Reorder the data blobs with the manifest_.
-  string ordered_blobs_path;
-  TEST_AND_RETURN_FALSE(utils::MakeTempFile(
-      "CrAU_temp_data.ordered.XXXXXX", &ordered_blobs_path, nullptr));
-  ScopedPathUnlinker ordered_blobs_unlinker(ordered_blobs_path);
-  TEST_AND_RETURN_FALSE(ReorderDataBlobs(data_blobs_path, ordered_blobs_path));
+  ScopedTempFile ordered_blobs_file("CrAU_temp_data.ordered.XXXXXX");
+  TEST_AND_RETURN_FALSE(
+      ReorderDataBlobs(data_blobs_path, ordered_blobs_file.path()));
 
   // Check that install op blobs are in order.
   uint64_t next_blob_offset = 0;
@@ -238,7 +229,7 @@
 
   // Append the data blobs.
   LOG(INFO) << "Writing final delta file data blobs...";
-  int blobs_fd = open(ordered_blobs_path.c_str(), O_RDONLY, 0);
+  int blobs_fd = open(ordered_blobs_file.path().c_str(), O_RDONLY, 0);
   ScopedFdCloser blobs_fd_closer(&blobs_fd);
   TEST_AND_RETURN_FALSE(blobs_fd >= 0);
   for (;;) {
diff --git a/payload_generator/payload_file_unittest.cc b/payload_generator/payload_file_unittest.cc
index 45faebb..1fd36f5 100644
--- a/payload_generator/payload_file_unittest.cc
+++ b/payload_generator/payload_file_unittest.cc
@@ -36,7 +36,7 @@
 };
 
 TEST_F(PayloadFileTest, ReorderBlobsTest) {
-  test_utils::ScopedTempFile orig_blobs("ReorderBlobsTest.orig.XXXXXX");
+  ScopedTempFile orig_blobs("ReorderBlobsTest.orig.XXXXXX");
 
   // The operations have three blob and one gap (the whitespace):
   // Rootfs operation 1: [8, 3] bcd
@@ -45,7 +45,7 @@
   string orig_data = "kernel abcd";
   EXPECT_TRUE(test_utils::WriteFileString(orig_blobs.path(), orig_data));
 
-  test_utils::ScopedTempFile new_blobs("ReorderBlobsTest.new.XXXXXX");
+  ScopedTempFile new_blobs("ReorderBlobsTest.new.XXXXXX");
 
   payload_.part_vec_.resize(2);
 
diff --git a/payload_generator/payload_generation_config.cc b/payload_generator/payload_generation_config.cc
index 9c5832d..ef2f240 100644
--- a/payload_generator/payload_generation_config.cc
+++ b/payload_generator/payload_generation_config.cc
@@ -103,7 +103,6 @@
 }
 
 bool ImageConfig::ValidateIsEmpty() const {
-  TEST_AND_RETURN_FALSE(ImageInfoIsEmpty());
   return partitions.empty();
 }
 
@@ -215,13 +214,6 @@
   return true;
 }
 
-bool ImageConfig::ImageInfoIsEmpty() const {
-  return image_info.board().empty() && image_info.key().empty() &&
-         image_info.channel().empty() && image_info.version().empty() &&
-         image_info.build_channel().empty() &&
-         image_info.build_version().empty();
-}
-
 PayloadVersion::PayloadVersion(uint64_t major_version, uint32_t minor_version) {
   major = major_version;
   minor = minor_version;
@@ -293,9 +285,6 @@
       TEST_AND_RETURN_FALSE(part.verity.IsEmpty());
     }
 
-    // If new_image_info is present, old_image_info must be present.
-    TEST_AND_RETURN_FALSE(source.ImageInfoIsEmpty() ==
-                          target.ImageInfoIsEmpty());
   } else {
     // All the "source" image fields must be empty for full payloads.
     TEST_AND_RETURN_FALSE(source.ValidateIsEmpty());
diff --git a/payload_generator/payload_generation_config.h b/payload_generator/payload_generation_config.h
index ec63043..1d88101 100644
--- a/payload_generator/payload_generation_config.h
+++ b/payload_generator/payload_generation_config.h
@@ -149,13 +149,6 @@
   // Validate |dynamic_partition_metadata| against |partitions|.
   bool ValidateDynamicPartitionMetadata() const;
 
-  // Returns whether the |image_info| field is empty.
-  bool ImageInfoIsEmpty() const;
-
-  // The ImageInfo message defined in the update_metadata.proto file describes
-  // the metadata of the image.
-  ImageInfo image_info;
-
   // The updated partitions.
   std::vector<PartitionConfig> partitions;
 
diff --git a/payload_generator/payload_generation_config_android_unittest.cc b/payload_generator/payload_generation_config_android_unittest.cc
index 44eaf55..e87b034 100644
--- a/payload_generator/payload_generation_config_android_unittest.cc
+++ b/payload_generator/payload_generation_config_android_unittest.cc
@@ -138,8 +138,7 @@
   }
 
   ImageConfig image_config_;
-  test_utils::ScopedTempFile temp_file_{
-      "PayloadGenerationConfigAndroidTest.XXXXXX"};
+  ScopedTempFile temp_file_{"PayloadGenerationConfigAndroidTest.XXXXXX"};
 };
 
 TEST_F(PayloadGenerationConfigAndroidTest, LoadVerityConfigSimpleTest) {
diff --git a/payload_generator/payload_properties.cc b/payload_generator/payload_properties.cc
index bc82eb7..bcf4fbd 100644
--- a/payload_generator/payload_properties.cc
+++ b/payload_generator/payload_properties.cc
@@ -47,8 +47,6 @@
 // These are needed by the Nebraska and devserver.
 const char kPayloadPropertyJsonPayloadSize[] = "size";
 const char kPayloadPropertyJsonIsDelta[] = "is_delta";
-const char kPayloadPropertyJsonTargetVersion[] = "target_version";
-const char kPayloadPropertyJsonSourceVersion[] = "source_version";
 }  // namespace
 
 PayloadProperties::PayloadProperties(const string& payload_path)
@@ -65,10 +63,6 @@
   properties.SetInteger(kPayloadPropertyJsonPayloadSize, payload_size_);
   properties.SetString(kPayloadPropertyJsonPayloadHash, payload_hash_);
   properties.SetBoolean(kPayloadPropertyJsonIsDelta, is_delta_);
-  properties.SetString(kPayloadPropertyJsonTargetVersion, target_version_);
-  if (is_delta_) {
-    properties.SetString(kPayloadPropertyJsonSourceVersion, source_version_);
-  }
 
   return base::JSONWriter::Write(properties, json_str);
 }
@@ -119,23 +113,11 @@
     metadata_signatures_ = base::JoinString(base64_signatures, ":");
   }
 
-  is_delta_ = manifest.has_old_image_info() ||
-              std::any_of(manifest.partitions().begin(),
+  is_delta_ = std::any_of(manifest.partitions().begin(),
                           manifest.partitions().end(),
                           [](const PartitionUpdate& part) {
                             return part.has_old_partition_info();
                           });
-
-  if (manifest.has_new_image_info()) {
-    target_version_ = manifest.new_image_info().version();
-  } else {
-    target_version_ = "99999.0.0";
-  }
-
-  // No need to set the source version if it was not a delta payload.
-  if (is_delta_ && manifest.has_old_image_info()) {
-    source_version_ = manifest.old_image_info().version();
-  }
   return true;
 }
 
diff --git a/payload_generator/payload_properties.h b/payload_generator/payload_properties.h
index 3b34511..846b181 100644
--- a/payload_generator/payload_properties.h
+++ b/payload_generator/payload_properties.h
@@ -62,9 +62,6 @@
   // Whether the payload is a delta (true) or full (false).
   bool is_delta_;
 
-  std::string target_version_;
-  std::string source_version_;
-
   DISALLOW_COPY_AND_ASSIGN(PayloadProperties);
 };
 
diff --git a/payload_generator/payload_properties_unittest.cc b/payload_generator/payload_properties_unittest.cc
index e0072fc..ed936ff 100644
--- a/payload_generator/payload_properties_unittest.cc
+++ b/payload_generator/payload_properties_unittest.cc
@@ -40,7 +40,6 @@
 #include "update_engine/payload_generator/payload_file.h"
 #include "update_engine/payload_generator/payload_generation_config.h"
 
-using chromeos_update_engine::test_utils::ScopedTempFile;
 using std::string;
 using std::unique_ptr;
 using std::vector;
@@ -57,19 +56,9 @@
     PayloadGenerationConfig config;
     config.version.major = kBrilloMajorPayloadVersion;
     config.version.minor = kSourceMinorPayloadVersion;
-    config.source.image_info.set_version("123.0.0");
-    config.target.image_info.set_version("456.7.8");
     PayloadFile payload;
     EXPECT_TRUE(payload.Init(config));
 
-    const string kTempFileTemplate = "temp_data.XXXXXX";
-    int data_file_fd;
-    string temp_file_path;
-    EXPECT_TRUE(
-        utils::MakeTempFile(kTempFileTemplate, &temp_file_path, &data_file_fd));
-    ScopedPathUnlinker temp_file_unlinker(temp_file_path);
-    EXPECT_LE(0, data_file_fd);
-
     const auto SetupPartitionConfig =
         [](PartitionConfig* config, const string& path, size_t size) {
           config->path = path;
@@ -79,8 +68,8 @@
       string zeros(size, '\0');
       EXPECT_TRUE(utils::WriteFile(path, zeros.c_str(), zeros.size()));
     };
-    ScopedTempFile old_part_file;
-    ScopedTempFile new_part_file;
+    ScopedTempFile old_part_file("old_part.XXXXXX");
+    ScopedTempFile new_part_file("new_part.XXXXXX");
     PartitionConfig old_part(kPartitionNameRoot);
     PartitionConfig new_part(kPartitionNameRoot);
     SetupPartitionConfig(&old_part, old_part_file.path(), 0);
@@ -93,7 +82,8 @@
 
     vector<AnnotatedOperation> aops;
     off_t data_file_size = 0;
-    BlobFileWriter blob_file_writer(data_file_fd, &data_file_size);
+    ScopedTempFile data_file("temp_data.XXXXXX", true);
+    BlobFileWriter blob_file_writer(data_file.fd(), &data_file_size);
     // Generate the operations using the strategy we selected above.
     EXPECT_TRUE(strategy->GenerateOperations(
         config, old_part, new_part, &blob_file_writer, &aops));
@@ -102,10 +92,10 @@
 
     uint64_t metadata_size;
     EXPECT_TRUE(payload.WritePayload(
-        payload_file.path(), temp_file_path, "", &metadata_size));
+        payload_file_.path(), data_file.path(), "", &metadata_size));
   }
 
-  ScopedTempFile payload_file;
+  ScopedTempFile payload_file_{"payload_file.XXXXXX"};
 };
 
 // Validate the hash of file exists within the output.
@@ -114,28 +104,26 @@
       "{"
       R"("is_delta":true,)"
       R"("metadata_signature":"",)"
-      R"("metadata_size":187,)"
-      R"("sha256_hex":"Rtrj9v3xXhrAi1741HAojtGxAQEOZ7mDyhzskIF4PJc=",)"
-      R"("size":233,)"
-      R"("source_version":"123.0.0",)"
-      R"("target_version":"456.7.8",)"
+      R"("metadata_size":165,)"
+      R"("sha256_hex":"cV7kfZBH3K0B6QJHxxykDh6b6x0WgVOmc63whPLOy7U=",)"
+      R"("size":211,)"
       R"("version":2)"
       "}";
   string json;
   EXPECT_TRUE(
-      PayloadProperties(payload_file.path()).GetPropertiesAsJson(&json));
+      PayloadProperties(payload_file_.path()).GetPropertiesAsJson(&json));
   EXPECT_EQ(kJsonProperties, json) << "JSON contents:\n" << json;
 }
 
 // Validate the hash of file and metadata are within the output.
 TEST_F(PayloadPropertiesTest, GetPropertiesAsKeyValueTestHash) {
   constexpr char kKeyValueProperties[] =
-      "FILE_HASH=Rtrj9v3xXhrAi1741HAojtGxAQEOZ7mDyhzskIF4PJc=\n"
-      "FILE_SIZE=233\n"
-      "METADATA_HASH=kiXTexy/s2aPttf4+r8KRZWYZ6FYvwhU6rJGcnnI+U0=\n"
-      "METADATA_SIZE=187\n";
+      "FILE_HASH=cV7kfZBH3K0B6QJHxxykDh6b6x0WgVOmc63whPLOy7U=\n"
+      "FILE_SIZE=211\n"
+      "METADATA_HASH=aEKYyzJt2E8Gz8fzB+gmekN5mriotZCSq6R+kDfdeV4=\n"
+      "METADATA_SIZE=165\n";
   string key_value;
-  EXPECT_TRUE(PayloadProperties{payload_file.path()}.GetPropertiesAsKeyValue(
+  EXPECT_TRUE(PayloadProperties{payload_file_.path()}.GetPropertiesAsKeyValue(
       &key_value));
   EXPECT_EQ(kKeyValueProperties, key_value) << "Key Value contents:\n"
                                             << key_value;
diff --git a/payload_generator/payload_signer.cc b/payload_generator/payload_signer.cc
index c3264c1..dd87ab7 100644
--- a/payload_generator/payload_signer.cc
+++ b/payload_generator/payload_signer.cc
@@ -321,7 +321,6 @@
                                                  signature.data(),
                                                  rsa,
                                                  RSA_NO_PADDING);
-
     if (signature_size < 0) {
       LOG(ERROR) << "Signing hash failed: "
                  << ERR_error_string(ERR_get_error(), nullptr);
diff --git a/payload_generator/payload_signer_unittest.cc b/payload_generator/payload_signer_unittest.cc
index fe62997..2a0b394 100644
--- a/payload_generator/payload_signer_unittest.cc
+++ b/payload_generator/payload_signer_unittest.cc
@@ -167,7 +167,7 @@
 }
 
 TEST_F(PayloadSignerTest, SkipMetadataSignatureTest) {
-  test_utils::ScopedTempFile payload_file("payload.XXXXXX");
+  ScopedTempFile payload_file("payload.XXXXXX");
   PayloadGenerationConfig config;
   config.version.major = kBrilloMajorPayloadVersion;
   PayloadFile payload;
@@ -194,7 +194,7 @@
 }
 
 TEST_F(PayloadSignerTest, VerifySignedPayloadTest) {
-  test_utils::ScopedTempFile payload_file("payload.XXXXXX");
+  ScopedTempFile payload_file("payload.XXXXXX");
   PayloadGenerationConfig config;
   config.version.major = kBrilloMajorPayloadVersion;
   PayloadFile payload;
diff --git a/payload_generator/squashfs_filesystem.cc b/payload_generator/squashfs_filesystem.cc
index 6152d7d..a41e283 100644
--- a/payload_generator/squashfs_filesystem.cc
+++ b/payload_generator/squashfs_filesystem.cc
@@ -72,15 +72,10 @@
 }
 
 bool GetFileMapContent(const string& sqfs_path, string* map) {
-  // Create a tmp file
-  string map_file;
-  TEST_AND_RETURN_FALSE(
-      utils::MakeTempFile("squashfs_file_map.XXXXXX", &map_file, nullptr));
-  ScopedPathUnlinker map_unlinker(map_file);
-
+  ScopedTempFile map_file("squashfs_file_map.XXXXXX");
   // Run unsquashfs to get the system file map.
   // unsquashfs -m <map-file> <squashfs-file>
-  vector<string> cmd = {"unsquashfs", "-m", map_file, sqfs_path};
+  vector<string> cmd = {"unsquashfs", "-m", map_file.path(), sqfs_path};
   string stdout, stderr;
   int exit_code;
   if (!Subprocess::SynchronousExec(cmd, &exit_code, &stdout, &stderr) ||
@@ -89,7 +84,7 @@
                << stdout << " and stderr content: " << stderr;
     return false;
   }
-  TEST_AND_RETURN_FALSE(utils::ReadFile(map_file, map));
+  TEST_AND_RETURN_FALSE(utils::ReadFile(map_file.path(), map));
   return true;
 }
 
diff --git a/payload_state.cc b/payload_state.cc
index 4945fe7..1d1583b 100644
--- a/payload_state.cc
+++ b/payload_state.cc
@@ -375,6 +375,7 @@
     case ErrorCode::kUnresolvedHostRecovered:
     case ErrorCode::kNotEnoughSpace:
     case ErrorCode::kDeviceCorrupted:
+    case ErrorCode::kPackageExcludedFromUpdate:
       LOG(INFO) << "Not incrementing URL index or failure count for this error";
       break;
 
@@ -463,6 +464,7 @@
 }
 
 void PayloadState::IncrementFullPayloadAttemptNumber() {
+  DCHECK(payload_index_ < response_.packages.size());
   // Update the payload attempt number for full payloads and the backoff time.
   if (response_.packages[payload_index_].is_delta) {
     LOG(INFO) << "Not incrementing payload attempt number for delta payloads";
@@ -475,6 +477,7 @@
 }
 
 void PayloadState::IncrementUrlIndex() {
+  DCHECK(payload_index_ < candidate_urls_.size());
   size_t next_url_index = url_index_ + 1;
   size_t max_url_size = candidate_urls_[payload_index_].size();
   if (next_url_index < max_url_size) {
@@ -511,6 +514,10 @@
 }
 
 void PayloadState::ExcludeCurrentPayload() {
+  if (payload_index_ >= response_.packages.size()) {
+    LOG(INFO) << "Skipping exclusion of the current payload.";
+    return;
+  }
   const auto& package = response_.packages[payload_index_];
   if (!package.can_exclude) {
     LOG(INFO) << "Not excluding as marked non-excludable for package hash="
@@ -613,10 +620,6 @@
   return kPayloadTypeForcedFull;
 }
 
-// TODO(zeuthen): Currently we don't report the UpdateEngine.Attempt.*
-// metrics if the attempt ends abnormally, e.g. if the update_engine
-// process crashes or the device is rebooted. See
-// http://crbug.com/357676
 void PayloadState::CollectAndReportAttemptMetrics(ErrorCode code) {
   int attempt_number = GetPayloadAttemptNumber();
 
@@ -671,6 +674,7 @@
     case metrics::AttemptResult::kAbnormalTermination:
     case metrics::AttemptResult::kUpdateCanceled:
     case metrics::AttemptResult::kUpdateSucceededNotActive:
+    case metrics::AttemptResult::kUpdateSkipped:
     case metrics::AttemptResult::kNumConstants:
     case metrics::AttemptResult::kUnset:
       break;
@@ -924,10 +928,12 @@
 }
 
 bool PayloadState::NextPayload() {
-  if (payload_index_ + 1 >= candidate_urls_.size())
+  if (payload_index_ >= candidate_urls_.size())
+    return false;
+  SetPayloadIndex(payload_index_ + 1);
+  if (payload_index_ >= candidate_urls_.size())
     return false;
   SetUrlIndex(0);
-  SetPayloadIndex(payload_index_ + 1);
   return true;
 }
 
diff --git a/payload_state.h b/payload_state.h
index 427836b..77197a7 100644
--- a/payload_state.h
+++ b/payload_state.h
@@ -161,6 +161,8 @@
   FRIEND_TEST(PayloadStateTest, ExcludeNoopForNonExcludables);
   FRIEND_TEST(PayloadStateTest, ExcludeOnlyCanExcludables);
   FRIEND_TEST(PayloadStateTest, IncrementFailureExclusionTest);
+  FRIEND_TEST(PayloadStateTest, HaltExclusionPostPayloadExhaustion);
+  FRIEND_TEST(PayloadStateTest, NonInfinitePayloadIndexIncrement);
 
   // Helper called when an attempt has begun, is called by
   // UpdateResumed(), UpdateRestarted() and Rollback().
diff --git a/payload_state_unittest.cc b/payload_state_unittest.cc
index c33bda4..2d571c1 100644
--- a/payload_state_unittest.cc
+++ b/payload_state_unittest.cc
@@ -630,7 +630,7 @@
   PayloadState payload_state;
   FakeSystemState fake_system_state;
   OmahaRequestParams params(&fake_system_state);
-  params.Init("", "", true);  // interactive = True.
+  params.Init("", "", {.interactive = true});
   fake_system_state.set_request_params(&params);
 
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
@@ -653,7 +653,7 @@
   PayloadState payload_state;
   FakeSystemState fake_system_state;
   OmahaRequestParams params(&fake_system_state);
-  params.Init("", "", false);  // interactive = False.
+  params.Init("", "", {});
   fake_system_state.set_request_params(&params);
 
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
@@ -1019,7 +1019,7 @@
   // Mock out the os version and make sure it's excluded correctly.
   string rollback_version = "2345.0.0";
   OmahaRequestParams params(&fake_system_state);
-  params.Init(rollback_version, "", false);
+  params.Init(rollback_version, "", {});
   fake_system_state.set_request_params(&params);
 
   EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
@@ -1778,4 +1778,49 @@
   payload_state.IncrementFailureCount();
 }
 
+TEST(PayloadStateTest, HaltExclusionPostPayloadExhaustion) {
+  PayloadState payload_state;
+  FakeSystemState fake_system_state;
+  StrictMock<MockExcluder> mock_excluder;
+  EXPECT_CALL(*fake_system_state.mock_update_attempter(), GetExcluder())
+      .WillOnce(Return(&mock_excluder));
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+  OmahaResponse response;
+  // Non-critical package.
+  response.packages.push_back(
+      {.payload_urls = {"http://test1a", "http://test2a"},
+       .size = 123456789,
+       .metadata_size = 58123,
+       .metadata_signature = "msign",
+       .hash = "hash",
+       .can_exclude = true});
+  payload_state.SetResponse(response);
+
+  // Exclusion should be called when excluded.
+  EXPECT_CALL(mock_excluder, Exclude(utils::GetExclusionName("http://test1a")))
+      .WillOnce(Return(true));
+  payload_state.ExcludeCurrentPayload();
+
+  // No more paylods to go through.
+  EXPECT_FALSE(payload_state.NextPayload());
+
+  // Exclusion should not be called as all |Payload|s are exhausted.
+  payload_state.ExcludeCurrentPayload();
+}
+
+TEST(PayloadStateTest, NonInfinitePayloadIndexIncrement) {
+  PayloadState payload_state;
+  FakeSystemState fake_system_state;
+  EXPECT_TRUE(payload_state.Initialize(&fake_system_state));
+
+  payload_state.SetResponse({});
+
+  EXPECT_FALSE(payload_state.NextPayload());
+  int payload_index = payload_state.payload_index_;
+
+  EXPECT_FALSE(payload_state.NextPayload());
+  EXPECT_EQ(payload_index, payload_state.payload_index_);
+}
+
 }  // namespace chromeos_update_engine
diff --git a/real_system_state.cc b/real_system_state.cc
index 74a37f3..924271e 100644
--- a/real_system_state.cc
+++ b/real_system_state.cc
@@ -138,7 +138,7 @@
   // will be re-initialized before every request using the actual request
   // options. This initialization here pre-loads current channel and version, so
   // the DBus service can access it.
-  if (!request_params_.Init("", "", false)) {
+  if (!request_params_.Init("", "", {})) {
     LOG(WARNING) << "Ignoring OmahaRequestParams initialization error. Some "
                     "features might not work properly.";
   }
diff --git a/requisition_util.cc b/requisition_util.cc
new file mode 100644
index 0000000..5445bce
--- /dev/null
+++ b/requisition_util.cc
@@ -0,0 +1,69 @@
+//
+// Copyright (C) 2020 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/requisition_util.h"
+
+#include <memory>
+#include <vector>
+
+#include <base/files/file_util.h>
+#include <base/json/json_file_value_serializer.h>
+#include <base/logging.h>
+#include <base/strings/string_util.h>
+
+#include "update_engine/common/subprocess.h"
+#include "update_engine/common/utils.h"
+
+using std::string;
+using std::vector;
+
+namespace {
+
+constexpr char kOemRequisitionKey[] = "oem_device_requisition";
+
+}  // namespace
+
+namespace chromeos_update_engine {
+
+string ReadDeviceRequisition(const base::FilePath& local_state) {
+  string requisition;
+  bool vpd_retval = utils::GetVpdValue(kOemRequisitionKey, &requisition);
+
+  // Some users manually convert non-CfM hardware at enrollment time, so VPD
+  // value may be missing. So check the Local State JSON as well.
+  if ((requisition.empty() || !vpd_retval) && base::PathExists(local_state)) {
+    int error_code;
+    std::string error_msg;
+    JSONFileValueDeserializer deserializer(local_state);
+    std::unique_ptr<base::Value> root =
+        deserializer.Deserialize(&error_code, &error_msg);
+    if (!root) {
+      if (error_code != 0) {
+        LOG(ERROR) << "Unable to deserialize Local State with exit code: "
+                   << error_code << " and error: " << error_msg;
+      }
+      return "";
+    }
+    auto* path = root->FindPath({"enrollment", "device_requisition"});
+    if (!path || !path->is_string()) {
+      return "";
+    }
+    path->GetAsString(&requisition);
+  }
+  return requisition;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/requisition_util.h b/requisition_util.h
new file mode 100644
index 0000000..8577ee7
--- /dev/null
+++ b/requisition_util.h
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2020 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_REQUISITION_UTIL_H_
+#define UPDATE_ENGINE_REQUISITION_UTIL_H_
+
+#include <string>
+
+#include <base/files/file_path.h>
+
+namespace chromeos_update_engine {
+
+// Checks the VPD and Local State for the device's requisition and returns it,
+// or an empty string if the device has no requisition.
+std::string ReadDeviceRequisition(const base::FilePath& local_state);
+
+}  // namespace chromeos_update_engine
+
+#endif  //  UPDATE_ENGINE_REQUISITION_UTIL_H_
diff --git a/requisition_util_unittest.cc b/requisition_util_unittest.cc
new file mode 100644
index 0000000..c21c9c7
--- /dev/null
+++ b/requisition_util_unittest.cc
@@ -0,0 +1,94 @@
+//
+// Copyright (C) 2020 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/requisition_util.h"
+
+#include <string>
+
+#include <base/files/file_path.h>
+#include <base/files/file_util.h>
+#include <base/files/scoped_temp_dir.h>
+#include <gtest/gtest.h>
+
+#include "update_engine/common/test_utils.h"
+
+using chromeos_update_engine::test_utils::WriteFileString;
+using std::string;
+
+namespace {
+
+const char kRemoraJSON[] =
+    "{\n"
+    "   \"the_list\": [ \"val1\", \"val2\" ],\n"
+    "   \"enrollment\": {\n"
+    "      \"autostart\": true,\n"
+    "      \"can_exit\": false,\n"
+    "      \"device_requisition\": \"remora\"\n"
+    "   },\n"
+    "   \"some_String\": \"1337\",\n"
+    "   \"some_int\": 42\n"
+    "}\n";
+
+const char kNoEnrollmentJSON[] =
+    "{\n"
+    "   \"the_list\": [ \"val1\", \"val2\" ],\n"
+    "   \"enrollment\": {\n"
+    "      \"autostart\": true,\n"
+    "      \"can_exit\": false,\n"
+    "      \"device_requisition\": \"\"\n"
+    "   },\n"
+    "   \"some_String\": \"1337\",\n"
+    "   \"some_int\": 42\n"
+    "}\n";
+}  // namespace
+
+namespace chromeos_update_engine {
+
+class RequisitionUtilTest : public ::testing::Test {
+ protected:
+  void SetUp() override { ASSERT_TRUE(root_dir_.CreateUniqueTempDir()); }
+
+  void WriteJsonToFile(const string& json) {
+    path_ =
+        base::FilePath(root_dir_.GetPath().value() + "/chronos/Local State");
+    ASSERT_TRUE(base::CreateDirectory(path_.DirName()));
+    ASSERT_TRUE(WriteFileString(path_.value(), json));
+  }
+
+  base::ScopedTempDir root_dir_;
+  base::FilePath path_;
+};
+
+TEST_F(RequisitionUtilTest, BadJsonReturnsEmpty) {
+  WriteJsonToFile("this isn't JSON");
+  EXPECT_EQ("", ReadDeviceRequisition(path_));
+}
+
+TEST_F(RequisitionUtilTest, NoFileReturnsEmpty) {
+  EXPECT_EQ("", ReadDeviceRequisition(path_));
+}
+
+TEST_F(RequisitionUtilTest, EnrollmentRequisition) {
+  WriteJsonToFile(kRemoraJSON);
+  EXPECT_EQ("remora", ReadDeviceRequisition(path_));
+}
+
+TEST_F(RequisitionUtilTest, BlankEnrollment) {
+  WriteJsonToFile(kNoEnrollmentJSON);
+  EXPECT_EQ("", ReadDeviceRequisition(path_));
+}
+
+}  // namespace chromeos_update_engine
diff --git a/sample_images/generate_payloads.sh b/sample_images/generate_payloads.sh
new file mode 100755
index 0000000..ee64229
--- /dev/null
+++ b/sample_images/generate_payloads.sh
@@ -0,0 +1,51 @@
+#!/bin/bash
+#
+# Copyright (C) 2020 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 script generates some sample payloads from the images in
+# sample_images.tar.bz2. and packages them in the sample_payloads.tar.xz file.
+# The payloads are then used in paycheck_unittests.py. The file names
+# must match the ones used in update_payload ebuild and paycheck_unittests.py.
+
+set -e
+
+TEMP_IMG_DIR=./sample_images
+OLD_KERNEL="${TEMP_IMG_DIR}/disk_ext2_4k_empty.img"
+OLD_ROOT="${TEMP_IMG_DIR}/disk_sqfs_empty.img"
+NEW_KERNEL="${TEMP_IMG_DIR}/disk_ext2_4k.img"
+NEW_ROOT="${TEMP_IMG_DIR}/disk_sqfs_default.img"
+
+
+mkdir -p "${TEMP_IMG_DIR}"
+tar -xvf sample_images.tar.bz2 -C "${TEMP_IMG_DIR}"
+
+echo "Generating full payload"
+delta_generator --out_file=full_payload.bin \
+                --partition_names=kernel:root \
+                --new_partitions="${NEW_KERNEL}":"${NEW_ROOT}"
+
+echo "Generating delta payload"
+delta_generator --out_file=delta_payload.bin \
+                --partition_names=kernel:root \
+                --new_partitions="${NEW_KERNEL}":"${NEW_ROOT}" \
+                --old_partitions="${OLD_KERNEL}":"${OLD_ROOT}" --minor_version=6
+
+echo "Creating sample_payloads.tar"
+tar -cJf sample_payloads.tar.xz {delta,full}_payload.bin
+
+rm -rf "${TEMP_IMG_DIR}" {delta,full}_payload.bin
+
+echo "Done"
diff --git a/sample_images/sample_payloads.tar.xz b/sample_images/sample_payloads.tar.xz
new file mode 100644
index 0000000..d0bf6d9
--- /dev/null
+++ b/sample_images/sample_payloads.tar.xz
Binary files differ
diff --git a/scripts/paycheck.py b/scripts/paycheck.py
index f4ccca2..cb1713f 100755
--- a/scripts/paycheck.py
+++ b/scripts/paycheck.py
@@ -27,6 +27,7 @@
 import sys
 import tempfile
 
+# pylint: disable=redefined-builtin
 from six.moves import zip
 from update_payload import error
 
@@ -92,9 +93,6 @@
   check_args.add_argument('-c', '--check', action='store_true', default=False,
                           help=('force payload integrity check (e.g. before '
                                 'applying)'))
-  check_args.add_argument('-D', '--describe', action='store_true',
-                          default=False,
-                          help='Print a friendly description of the payload.')
   check_args.add_argument('-r', '--report', metavar='FILE',
                           help="dump payload report (`-' for stdout)")
   check_args.add_argument('-t', '--type', dest='assert_type',
@@ -209,9 +207,6 @@
       # Initialize payload.
       payload.Init()
 
-      if args.describe:
-        payload.Describe()
-
       # Perform payload integrity checks.
       if args.check:
         report_file = None
diff --git a/scripts/paycheck_unittest.py b/scripts/paycheck_unittest.py
new file mode 100755
index 0000000..a90d269
--- /dev/null
+++ b/scripts/paycheck_unittest.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2020 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 paycheck.py."""
+
+# This test requires new (Y) and old (X) images, as well as a full payload
+# from image Y and a delta payload from Y to X for each partition.
+# Payloads are from sample_images/generate_payloads.
+#
+# The test performs the following:
+#
+# - It statically applies the full and delta payloads.
+#
+# - It applies full_payload to yield a new kernel (kern.part) and rootfs
+#   (root.part) and compares them to the new image partitions.
+#
+# - It applies delta_payload to the old image to yield a new kernel and rootfs
+#   and compares them to the new image partitions.
+#
+# Previously test_paycheck.sh. Run with update_payload ebuild.
+
+# Disable check for function names to avoid errors based on old code
+# pylint: disable=invalid-name
+
+import filecmp
+import os
+import subprocess
+import unittest
+
+
+class PaycheckTest(unittest.TestCase):
+  """Test paycheck functions."""
+
+  def setUp(self):
+    self.tmpdir = os.getenv('T')
+
+    self._full_payload = os.path.join(self.tmpdir, 'full_payload.bin')
+    self._delta_payload = os.path.join(self.tmpdir, 'delta_payload.bin')
+
+    self._new_kernel = os.path.join(self.tmpdir, 'disk_ext2_4k.img')
+    self._new_root = os.path.join(self.tmpdir, 'disk_sqfs_default.img')
+    self._old_kernel = os.path.join(self.tmpdir,
+                                    'disk_ext2_4k_empty.img')
+    self._old_root = os.path.join(self.tmpdir, 'disk_sqfs_empty.img')
+
+    # Temp output files.
+    self._kernel_part = os.path.join(self.tmpdir, 'kern.part')
+    self._root_part = os.path.join(self.tmpdir, 'root.part')
+
+  def checkPayload(self, type_arg, payload):
+    """Checks Payload."""
+    self.assertEqual(0, subprocess.check_call(['./paycheck.py', '-t',
+                                               type_arg, payload]))
+
+  def testFullPayload(self):
+    """Checks the full payload statically."""
+    self.checkPayload('full', self._full_payload)
+
+  def testDeltaPayload(self):
+    """Checks the delta payload statically."""
+    self.checkPayload('delta', self._delta_payload)
+
+  def testApplyFullPayload(self):
+    """Applies full payloads and compares results to new sample images."""
+    self.assertEqual(0, subprocess.check_call(['./paycheck.py',
+                                               self._full_payload,
+                                               '--part_names', 'kernel', 'root',
+                                               '--out_dst_part_paths',
+                                               self._kernel_part,
+                                               self._root_part]))
+
+    # Check if generated full image is equal to sample image.
+    self.assertTrue(filecmp.cmp(self._kernel_part, self._new_kernel))
+    self.assertTrue(filecmp.cmp(self._root_part, self._new_root))
+
+  def testApplyDeltaPayload(self):
+    """Applies delta to old image and checks against new sample images."""
+    self.assertEqual(0, subprocess.check_call(['./paycheck.py',
+                                               self._delta_payload,
+                                               '--part_names', 'kernel', 'root',
+                                               '--src_part_paths',
+                                               self._old_kernel, self._old_root,
+                                               '--out_dst_part_paths',
+                                               self._kernel_part,
+                                               self._root_part]))
+
+    self.assertTrue(filecmp.cmp(self._kernel_part, self._new_kernel))
+    self.assertTrue(filecmp.cmp(self._root_part, self._new_root))
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/scripts/run_unittests b/scripts/run_unittests
index 0d301ba..db5ed73 100755
--- a/scripts/run_unittests
+++ b/scripts/run_unittests
@@ -26,5 +26,6 @@
 done
 
 ./payload_info_unittest.py
+./paycheck_unittest.py
 
 exit 0
diff --git a/scripts/test_paycheck.sh b/scripts/test_paycheck.sh
deleted file mode 100755
index 239b984..0000000
--- a/scripts/test_paycheck.sh
+++ /dev/null
@@ -1,169 +0,0 @@
-#!/bin/bash
-#
-# Copyright (C) 2013 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.
-#
-
-# A test script for paycheck.py and the update_payload.py library.
-#
-# This script requires three payload files, along with a metadata signature for
-# each, and a public key for verifying signatures. Payload include:
-#
-# - A full payload for release X (old_full_payload)
-#
-# - A full payload for release Y (new_full_payload), where Y > X
-#
-# - A delta payload from X to Y (delta_payload)
-#
-# The test performs the following:
-#
-# - It verifies each payload against its metadata signature, also asserting the
-#   payload type. Another artifact is a human-readable payload report, which
-#   is output to stdout to be inspected by the user.
-#
-# - It applies old_full_payload to yield old kernel (old_kern.part) and rootfs
-#   (old_root.part) partitions.
-#
-# - It applies delta_payload to old_{kern,root}.part to yield new kernel
-#   (new_delta_kern.part) and rootfs (new_delta_root.part) partitions.
-#
-# - It applies new_full_payload to yield reference new kernel
-#   (new_full_kern.part) and rootfs (new_full_root.part) partitions.
-#
-# - It compares new_{delta,full}_kern.part and new_{delta,full}_root.part to
-#   ensure that they are binary identical.
-#
-# If all steps have completed successfully we know with high certainty that
-# paycheck.py (and hence update_payload.py) correctly parses both full and delta
-# payloads, and applies them to yield the expected result. Finally, each
-# paycheck.py execution is timed.
-
-
-# Stop on errors, unset variables.
-set -e
-set -u
-
-# Temporary image files.
-OLD_KERN_PART=old_kern.part
-OLD_ROOT_PART=old_root.part
-NEW_DELTA_KERN_PART=new_delta_kern.part
-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() {
-  echo "$@" >&2
-}
-
-die() {
-  log "$@"
-  exit 1
-}
-
-usage_and_exit() {
-  cat >&2 <<EOF
-Usage: ${0##*/} old_full_payload delta_payload new_full_payload
-EOF
-  exit
-}
-
-check_payload() {
-  payload_file=$1
-  payload_type=$2
-
-  time ${paycheck} -t ${payload_type} ${payload_file}
-}
-
-apply_full_payload() {
-  payload_file=$1
-  out_dst_kern_part="$2/$3"
-  out_dst_root_part="$2/$4"
-
-  time ${paycheck} ${payload_file} \
-    --part_names ${CROS_PARTS} \
-    --out_dst_part_paths ${out_dst_kern_part} ${out_dst_root_part}
-}
-
-apply_delta_payload() {
-  payload_file=$1
-  out_dst_kern_part="$2/$3"
-  out_dst_root_part="$2/$4"
-  dst_kern_part="$2/$5"
-  dst_root_part="$2/$6"
-  src_kern_part="$2/$7"
-  src_root_part="$2/$8"
-
-  time ${paycheck} ${payload_file} \
-    --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() {
-  # Read command-line arguments.
-  if [ $# == 1 ] && [ "$1" == "-h" ]; then
-    usage_and_exit
-  elif [ $# != 3 ]; then
-    die "Error: unexpected number of arguments"
-  fi
-  old_full_payload="$1"
-  delta_payload="$2"
-  new_full_payload="$3"
-
-  # Find paycheck.py
-  paycheck=${0%/*}/paycheck.py
-  if [ -z "${paycheck}" ] || [ ! -x ${paycheck} ]; then
-    die "cannot find ${paycheck} or file is not executable"
-  fi
-
-  # Check the payloads statically.
-  log "Checking payloads..."
-  check_payload "${old_full_payload}" full
-  check_payload "${new_full_payload}" full
-  check_payload "${delta_payload}" delta
-  log "Done"
-
-  # Apply full/delta payloads and verify results are identical.
-  tmpdir="$(mktemp -d --tmpdir test_paycheck.XXXXXXXX)"
-  log "Initiating application of payloads at $tmpdir"
-
-  log "Applying old full payload..."
-  apply_full_payload "${old_full_payload}" "${tmpdir}" "${OLD_KERN_PART}" \
-    "${OLD_ROOT_PART}"
-  log "Done"
-
-  log "Applying new full payload..."
-  apply_full_payload "${new_full_payload}" "${tmpdir}" "${NEW_FULL_KERN_PART}" \
-    "${NEW_FULL_ROOT_PART}"
-  log "Done"
-
-  log "Applying delta payload to old partitions..."
-  apply_delta_payload "${delta_payload}" "${tmpdir}" "${NEW_DELTA_KERN_PART}" \
-    "${NEW_DELTA_ROOT_PART}" "${NEW_FULL_KERN_PART}" \
-    "${NEW_FULL_ROOT_PART}" "${OLD_KERN_PART}" "${OLD_ROOT_PART}"
-  log "Done"
-
-  log "Comparing results of delta and new full updates..."
-  diff "${tmpdir}/${NEW_FULL_KERN_PART}" "${tmpdir}/${NEW_DELTA_KERN_PART}"
-  diff "${tmpdir}/${NEW_FULL_ROOT_PART}" "${tmpdir}/${NEW_DELTA_ROOT_PART}"
-  log "Done"
-
-  log "Cleaning up"
-  rm -fr "${tmpdir}"
-}
-
-main "$@"
diff --git a/scripts/update_payload/checker.py b/scripts/update_payload/checker.py
index 4c65516..56a9370 100644
--- a/scripts/update_payload/checker.py
+++ b/scripts/update_payload/checker.py
@@ -35,6 +35,7 @@
 import os
 import subprocess
 
+# pylint: disable=redefined-builtin
 from six.moves import range
 
 from update_payload import common
@@ -71,6 +72,7 @@
     4: (_TYPE_DELTA,),
     5: (_TYPE_DELTA,),
     6: (_TYPE_DELTA,),
+    7: (_TYPE_DELTA,),
 }
 
 
@@ -1148,17 +1150,13 @@
       sig_report = report.AddSubReport(sig_name)
 
       # Check: Signature contains mandatory fields.
-      self._CheckMandatoryField(sig, 'version', sig_report, sig_name)
       self._CheckMandatoryField(sig, 'data', None, sig_name)
       sig_report.AddField('data len', len(sig.data))
 
       # Check: Signatures pertains to actual payload hash.
-      if sig.version == 1:
+      if sig.data:
         self._CheckSha256Signature(sig.data, pubkey_file_name,
                                    payload_hasher.digest(), sig_name)
-      else:
-        raise error.PayloadError('Unknown signature version (%d).' %
-                                 sig.version)
 
   def Run(self, pubkey_file_name=None, metadata_sig_file=None, metadata_size=0,
           part_sizes=None, report_out_file=None):
diff --git a/scripts/update_payload/payload.py b/scripts/update_payload/payload.py
index 78b8e2c..fe3a450 100644
--- a/scripts/update_payload/payload.py
+++ b/scripts/update_payload/payload.py
@@ -232,31 +232,6 @@
 
     self.is_init = True
 
-  def Describe(self):
-    """Emits the payload embedded description data to standard output."""
-    def _DescribeImageInfo(description, image_info):
-      """Display info about the image."""
-      def _DisplayIndentedValue(name, value):
-        print('  {:<14} {}'.format(name+':', value))
-
-      print('%s:' % description)
-      _DisplayIndentedValue('Channel', image_info.channel)
-      _DisplayIndentedValue('Board', image_info.board)
-      _DisplayIndentedValue('Version', image_info.version)
-      _DisplayIndentedValue('Key', image_info.key)
-
-      if image_info.build_channel != image_info.channel:
-        _DisplayIndentedValue('Build channel', image_info.build_channel)
-
-      if image_info.build_version != image_info.version:
-        _DisplayIndentedValue('Build version', image_info.build_version)
-
-    if self.manifest.HasField('old_image_info'):
-      _DescribeImageInfo('Old Image', self.manifest.old_image_info)
-
-    if self.manifest.HasField('new_image_info'):
-      _DescribeImageInfo('New Image', self.manifest.new_image_info)
-
   def _AssertInit(self):
     """Raises an exception if the object was not initialized."""
     if not self.is_init:
diff --git a/test_http_server.cc b/test_http_server.cc
index 1c3a2e0..a2f1e05 100644
--- a/test_http_server.cc
+++ b/test_http_server.cc
@@ -190,7 +190,7 @@
                     string("HTTP/1.1 ") + Itoa(return_code) + " " +
                         GetHttpResponseDescription(return_code) +
                         EOL "Content-Type: application/octet-stream" EOL
-                        "Connection: close" EOL);
+                            "Connection: close" EOL);
   if (ret < 0)
     return -1;
   written += ret;
@@ -409,7 +409,6 @@
     return;
   WriteString(fd, "Connection: close" EOL);
   WriteString(fd, "Location: " + url + EOL);
-
 }
 
 // Generate a page not found error response with actual text payload. Return
diff --git a/update_attempter.cc b/update_attempter.cc
index c4fe348..38b0f82 100644
--- a/update_attempter.cc
+++ b/update_attempter.cc
@@ -244,15 +244,7 @@
   system_state_->metrics_reporter()->ReportDailyMetrics(age);
 }
 
-void UpdateAttempter::Update(const string& app_version,
-                             const string& omaha_url,
-                             const string& target_channel,
-                             const string& target_version_prefix,
-                             bool rollback_allowed,
-                             bool rollback_data_save_requested,
-                             int rollback_allowed_milestones,
-                             bool obey_proxies,
-                             bool interactive) {
+void UpdateAttempter::Update(const UpdateCheckParams& params) {
   // This is normally called frequently enough so it's appropriate to use as a
   // hook for reporting daily metrics.
   // TODO(garnold) This should be hooked to a separate (reliable and consistent)
@@ -281,19 +273,11 @@
     return;
   }
 
-  if (!CalculateUpdateParams(app_version,
-                             omaha_url,
-                             target_channel,
-                             target_version_prefix,
-                             rollback_allowed,
-                             rollback_data_save_requested,
-                             rollback_allowed_milestones,
-                             obey_proxies,
-                             interactive)) {
+  if (!CalculateUpdateParams(params)) {
     return;
   }
 
-  BuildUpdateActions(interactive);
+  BuildUpdateActions(params.interactive);
 
   SetStatusAndNotify(UpdateStatus::CHECKING_FOR_UPDATE);
 
@@ -356,15 +340,7 @@
   payload_state->SetUsingP2PForSharing(use_p2p_for_sharing);
 }
 
-bool UpdateAttempter::CalculateUpdateParams(const string& app_version,
-                                            const string& omaha_url,
-                                            const string& target_channel,
-                                            const string& target_version_prefix,
-                                            bool rollback_allowed,
-                                            bool rollback_data_save_requested,
-                                            int rollback_allowed_milestones,
-                                            bool obey_proxies,
-                                            bool interactive) {
+bool UpdateAttempter::CalculateUpdateParams(const UpdateCheckParams& params) {
   http_response_code_ = 0;
   PayloadStateInterface* const payload_state = system_state_->payload_state();
 
@@ -375,27 +351,13 @@
   // policy is available again.
   UpdateRollbackHappened();
 
-  // Update the target version prefix.
-  omaha_request_params_->set_target_version_prefix(target_version_prefix);
-
-  // Set whether rollback is allowed.
-  omaha_request_params_->set_rollback_allowed(rollback_allowed);
-
-  // Set whether saving data over rollback is requested.
-  omaha_request_params_->set_rollback_data_save_requested(
-      rollback_data_save_requested);
-
-  CalculateStagingParams(interactive);
+  CalculateStagingParams(params.interactive);
   // If staging_wait_time_ wasn't set, staging is off, use scattering instead.
   if (staging_wait_time_.InSeconds() == 0) {
-    CalculateScatteringParams(interactive);
+    CalculateScatteringParams(params.interactive);
   }
 
-  // Set how many milestones of rollback are allowed.
-  omaha_request_params_->set_rollback_allowed_milestones(
-      rollback_allowed_milestones);
-
-  CalculateP2PParams(interactive);
+  CalculateP2PParams(params.interactive);
   if (payload_state->GetUsingP2PForDownloading() ||
       payload_state->GetUsingP2PForSharing()) {
     // OK, p2p is to be used - start it and perform housekeeping.
@@ -408,32 +370,12 @@
     }
   }
 
-  if (!omaha_request_params_->Init(app_version, omaha_url, interactive)) {
+  if (!omaha_request_params_->Init(
+          forced_app_version_, forced_omaha_url_, params)) {
     LOG(ERROR) << "Unable to initialize Omaha request params.";
     return false;
   }
 
-  // Set the target channel, if one was provided.
-  if (target_channel.empty()) {
-    LOG(INFO) << "No target channel mandated by policy.";
-  } else {
-    LOG(INFO) << "Setting target channel as mandated: " << target_channel;
-    // Pass in false for powerwash_allowed until we add it to the policy
-    // protobuf.
-    string error_message;
-    if (!omaha_request_params_->SetTargetChannel(
-            target_channel, false, &error_message)) {
-      LOG(ERROR) << "Setting the channel failed: " << error_message;
-    }
-
-    // Since this is the beginning of a new attempt, update the download
-    // channel. The download channel won't be updated until the next attempt,
-    // even if target channel changes meanwhile, so that how we'll know if we
-    // should cancel the current download attempt if there's such a change in
-    // target channel.
-    omaha_request_params_->UpdateDownloadChannel();
-  }
-
   // The function |CalculateDlcParams| makes use of the function |GetAppId| from
   // |OmahaRequestParams|, so to ensure that the return from |GetAppId|
   // doesn't change, no changes to the values |download_channel_|,
@@ -441,8 +383,6 @@
   // |omaha_request_params_| shall be made below this line.
   CalculateDlcParams();
 
-  omaha_request_params_->set_is_install(is_install_);
-
   // Set Quick Fix Build token if policy is set and the device is enterprise
   // enrolled.
   string token;
@@ -473,7 +413,7 @@
             << payload_state->GetUsingP2PForSharing();
 
   obeying_proxies_ = true;
-  if (obey_proxies || proxy_manual_checks_ == 0) {
+  if (proxy_manual_checks_ == 0) {
     LOG(INFO) << "forced to obey proxies";
     // If forced to obey proxies, every 20th request will not use proxies
     proxy_manual_checks_++;
@@ -760,6 +700,7 @@
     dlc_apps_params[omaha_request_params_->GetDlcAppId(dlc_id)] = dlc_params;
   }
   omaha_request_params_->set_dlc_apps_params(dlc_apps_params);
+  omaha_request_params_->set_is_install(is_install_);
 }
 
 void UpdateAttempter::BuildUpdateActions(bool interactive) {
@@ -874,7 +815,7 @@
   processor_->set_delegate(this);
 
   // Initialize the default request params.
-  if (!omaha_request_params_->Init("", "", true)) {
+  if (!omaha_request_params_->Init("", "", {.interactive = true})) {
     LOG(ERROR) << "Unable to initialize Omaha request params.";
     return false;
   }
@@ -1100,15 +1041,7 @@
     LOG(INFO) << "Update attempt flags in use = 0x" << std::hex
               << current_update_attempt_flags_;
 
-    Update(forced_app_version_,
-           forced_omaha_url_,
-           params.target_channel,
-           params.target_version_prefix,
-           params.rollback_allowed,
-           params.rollback_data_save_requested,
-           params.rollback_allowed_milestones,
-           /*obey_proxies=*/false,
-           params.interactive);
+    Update(params);
     // Always clear the forced app_version and omaha_url after an update attempt
     // so the next update uses the defaults.
     forced_app_version_.clear();
diff --git a/update_attempter.h b/update_attempter.h
index dd958f5..3a1bef4 100644
--- a/update_attempter.h
+++ b/update_attempter.h
@@ -76,21 +76,8 @@
   virtual bool ScheduleUpdates();
 
   // Checks for update and, if a newer version is available, attempts to update
-  // the system. Non-empty |in_app_version| or |in_update_url| prevents
-  // automatic detection of the parameter.  |target_channel| denotes a
-  // policy-mandated channel we are updating to, if not empty. If |obey_proxies|
-  // is true, the update will likely respect Chrome's proxy setting. For
-  // security reasons, we may still not honor them. |interactive| should be true
-  // if this was called from the user (ie dbus).
-  virtual void Update(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 rollback_data_save_requested,
-                      int rollback_allowed_milestones,
-                      bool obey_proxies,
-                      bool interactive);
+  // the system.
+  virtual void Update(const chromeos_update_manager::UpdateCheckParams& params);
 
   // ActionProcessorDelegate methods:
   void ProcessingDone(const ActionProcessor* processor,
@@ -283,6 +270,8 @@
   FRIEND_TEST(UpdateAttempterTest, RollbackAfterInstall);
   FRIEND_TEST(UpdateAttempterTest, RollbackAllowed);
   FRIEND_TEST(UpdateAttempterTest, RollbackAllowedSetAndReset);
+  FRIEND_TEST(UpdateAttempterTest, ChannelDowngradeNoRollback);
+  FRIEND_TEST(UpdateAttempterTest, ChannelDowngradeRollback);
   FRIEND_TEST(UpdateAttempterTest, RollbackMetricsNotRollbackFailure);
   FRIEND_TEST(UpdateAttempterTest, RollbackMetricsNotRollbackSuccess);
   FRIEND_TEST(UpdateAttempterTest, RollbackMetricsRollbackFailure);
@@ -293,6 +282,7 @@
   FRIEND_TEST(UpdateAttempterTest, SessionIdTestOnOmahaRequestActions);
   FRIEND_TEST(UpdateAttempterTest, SetRollbackHappenedNotRollback);
   FRIEND_TEST(UpdateAttempterTest, SetRollbackHappenedRollback);
+  FRIEND_TEST(UpdateAttempterTest, TargetChannelHintSetAndReset);
   FRIEND_TEST(UpdateAttempterTest, TargetVersionPrefixSetAndReset);
   FRIEND_TEST(UpdateAttempterTest, UpdateAfterInstall);
   FRIEND_TEST(UpdateAttempterTest, UpdateAttemptFlagsCachedAtUpdateStart);
@@ -366,15 +356,8 @@
   // Helper method of Update() to calculate the update-related parameters
   // from various sources and set the appropriate state. Please refer to
   // Update() method for the meaning of the parameters.
-  bool CalculateUpdateParams(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 rollback_data_save_requested,
-                             int rollback_allowed_milestones,
-                             bool obey_proxies,
-                             bool interactive);
+  bool CalculateUpdateParams(
+      const chromeos_update_manager::UpdateCheckParams& params);
 
   // Calculates all the scattering related parameters (such as waiting period,
   // which type of scattering is enabled, etc.) and also updates/deletes
diff --git a/update_attempter_unittest.cc b/update_attempter_unittest.cc
index 305dbdb..767bb82 100644
--- a/update_attempter_unittest.cc
+++ b/update_attempter_unittest.cc
@@ -25,7 +25,8 @@
 #include <unordered_set>
 
 #include <base/files/file_util.h>
-#include <base/message_loop/message_loop.h>
+#include <base/files/scoped_temp_dir.h>
+#include <base/task/single_thread_task_executor.h>
 #include <brillo/message_loops/base_message_loop.h>
 #include <brillo/message_loops/message_loop.h>
 #include <brillo/message_loops/message_loop_utils.h>
@@ -171,26 +172,10 @@
   explicit UpdateAttempterUnderTest(SystemState* system_state)
       : UpdateAttempter(system_state, nullptr) {}
 
-  void Update(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 rollback_data_save_requested,
-              int rollback_allowed_milestones,
-              bool obey_proxies,
-              bool interactive) override {
+  void Update(const UpdateCheckParams& params) override {
     update_called_ = true;
     if (do_update_) {
-      UpdateAttempter::Update(app_version,
-                              omaha_url,
-                              target_channel,
-                              target_version_prefix,
-                              rollback_allowed,
-                              rollback_data_save_requested,
-                              rollback_allowed_milestones,
-                              obey_proxies,
-                              interactive);
+      UpdateAttempter::Update(params);
       return;
     }
     LOG(INFO) << "[TEST] Update() disabled.";
@@ -332,8 +317,8 @@
   // |ProcessingDone()| related member functions.
   void TestProcessingDone();
 
-  base::MessageLoopForIO base_loop_;
-  brillo::BaseMessageLoop loop_{&base_loop_};
+  base::SingleThreadTaskExecutor base_loop_{base::MessagePumpType::IO};
+  brillo::BaseMessageLoop loop_{base_loop_.task_runner()};
 
   FakeSystemState fake_system_state_;
   UpdateAttempterUnderTest attempter_{&fake_system_state_};
@@ -425,7 +410,7 @@
 void UpdateAttempterTest::SessionIdTestChange() {
   EXPECT_NE(UpdateStatus::UPDATED_NEED_REBOOT, attempter_.status());
   const auto old_session_id = attempter_.session_id_;
-  attempter_.Update("", "", "", "", false, false, 0, false, false);
+  attempter_.Update({});
   EXPECT_NE(old_session_id, attempter_.session_id_);
   ScheduleQuitMainLoop();
 }
@@ -796,7 +781,7 @@
     EXPECT_CALL(*processor_, StartProcessing());
   }
 
-  attempter_.Update("", "", "", "", false, false, 0, false, false);
+  attempter_.Update({});
   loop_.PostTask(FROM_HERE,
                  base::Bind(&UpdateAttempterTest::UpdateTestVerify,
                             base::Unretained(this)));
@@ -996,7 +981,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, 0, false, false);
+  attempter_.Update({});
   EXPECT_FALSE(actual_using_p2p_for_downloading_);
   EXPECT_FALSE(actual_using_p2p_for_sharing());
   ScheduleQuitMainLoop();
@@ -1018,7 +1003,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, 0, false, false);
+  attempter_.Update({});
   EXPECT_FALSE(actual_using_p2p_for_downloading());
   EXPECT_FALSE(actual_using_p2p_for_sharing());
   ScheduleQuitMainLoop();
@@ -1041,7 +1026,7 @@
   mock_p2p_manager.fake().SetEnsureP2PRunningResult(true);
   mock_p2p_manager.fake().SetPerformHousekeepingResult(false);
   EXPECT_CALL(mock_p2p_manager, PerformHousekeeping());
-  attempter_.Update("", "", "", "", false, false, 0, false, false);
+  attempter_.Update({});
   EXPECT_FALSE(actual_using_p2p_for_downloading());
   EXPECT_FALSE(actual_using_p2p_for_sharing());
   ScheduleQuitMainLoop();
@@ -1063,7 +1048,7 @@
   mock_p2p_manager.fake().SetEnsureP2PRunningResult(true);
   mock_p2p_manager.fake().SetPerformHousekeepingResult(true);
   EXPECT_CALL(mock_p2p_manager, PerformHousekeeping());
-  attempter_.Update("", "", "", "", false, false, 0, false, false);
+  attempter_.Update({});
   EXPECT_TRUE(actual_using_p2p_for_downloading());
   EXPECT_TRUE(actual_using_p2p_for_sharing());
   ScheduleQuitMainLoop();
@@ -1086,15 +1071,7 @@
   mock_p2p_manager.fake().SetEnsureP2PRunningResult(true);
   mock_p2p_manager.fake().SetPerformHousekeepingResult(true);
   EXPECT_CALL(mock_p2p_manager, PerformHousekeeping());
-  attempter_.Update("",
-                    "",
-                    "",
-                    "",
-                    false,
-                    false,
-                    /*rollback_allowed_milestones=*/0,
-                    false,
-                    /*interactive=*/true);
+  attempter_.Update({.interactive = true});
   EXPECT_FALSE(actual_using_p2p_for_downloading());
   EXPECT_TRUE(actual_using_p2p_for_sharing());
   ScheduleQuitMainLoop();
@@ -1124,7 +1101,7 @@
   attempter_.policy_provider_.reset(
       new policy::PolicyProvider(std::move(device_policy)));
 
-  attempter_.Update("", "", "", "", false, false, 0, false, false);
+  attempter_.Update({});
   EXPECT_EQ(scatter_factor_in_seconds, attempter_.scatter_factor_.InSeconds());
 
   ScheduleQuitMainLoop();
@@ -1162,7 +1139,7 @@
   attempter_.policy_provider_.reset(
       new policy::PolicyProvider(std::move(device_policy)));
 
-  attempter_.Update("", "", "", "", false, false, 0, false, false);
+  attempter_.Update({});
   EXPECT_EQ(scatter_factor_in_seconds, attempter_.scatter_factor_.InSeconds());
 
   // Make sure the file still exists.
@@ -1178,7 +1155,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, 0, false, false);
+  attempter_.Update({});
   EXPECT_TRUE(fake_prefs.Exists(kPrefsUpdateCheckCount));
   EXPECT_TRUE(fake_prefs.GetInt64(kPrefsUpdateCheckCount, &new_value));
   EXPECT_EQ(initial_value, new_value);
@@ -1225,15 +1202,7 @@
       new policy::PolicyProvider(std::move(device_policy)));
 
   // Trigger an interactive check so we can test that scattering is disabled.
-  attempter_.Update("",
-                    "",
-                    "",
-                    "",
-                    false,
-                    false,
-                    /*rollback_allowed_milestones=*/0,
-                    false,
-                    /*interactive=*/true);
+  attempter_.Update({.interactive = true});
   EXPECT_EQ(scatter_factor_in_seconds, attempter_.scatter_factor_.InSeconds());
 
   // Make sure scattering is disabled for manual (i.e. user initiated) update
@@ -1285,7 +1254,7 @@
   FakePrefs fake_prefs;
   SetUpStagingTest(kValidStagingSchedule, &fake_prefs);
 
-  attempter_.Update("", "", "", "", false, false, 0, false, false);
+  attempter_.Update({});
   // Check that prefs have the correct values.
   int64_t update_count;
   EXPECT_TRUE(fake_prefs.GetInt64(kPrefsUpdateCheckCount, &update_count));
@@ -1342,8 +1311,7 @@
   FakePrefs fake_prefs;
   SetUpStagingTest(kValidStagingSchedule, &fake_prefs);
 
-  attempter_.Update(
-      "", "", "", "", false, false, 0, false, /* interactive = */ true);
+  attempter_.Update({.interactive = true});
   CheckStagingOff();
 
   ScheduleQuitMainLoop();
@@ -1363,8 +1331,7 @@
   FakePrefs fake_prefs;
   SetUpStagingTest(kValidStagingSchedule, &fake_prefs);
 
-  attempter_.Update(
-      "", "", "", "", false, false, 0, false, /* interactive = */ true);
+  attempter_.Update({.interactive = true});
   CheckStagingOff();
 
   ScheduleQuitMainLoop();
@@ -1692,45 +1659,64 @@
 }
 
 TEST_F(UpdateAttempterTest, TargetVersionPrefixSetAndReset) {
-  attempter_.CalculateUpdateParams(
-      "", "", "", "1234", false, false, 4, false, false);
+  UpdateCheckParams params;
+  attempter_.CalculateUpdateParams({.target_version_prefix = "1234"});
   EXPECT_EQ("1234",
             fake_system_state_.request_params()->target_version_prefix());
 
-  attempter_.CalculateUpdateParams(
-      "", "", "", "", false, 4, false, false, false);
+  attempter_.CalculateUpdateParams({});
   EXPECT_TRUE(
       fake_system_state_.request_params()->target_version_prefix().empty());
 }
 
+TEST_F(UpdateAttempterTest, TargetChannelHintSetAndReset) {
+  attempter_.CalculateUpdateParams({.lts_tag = "hint"});
+  EXPECT_EQ("hint", fake_system_state_.request_params()->lts_tag());
+
+  attempter_.CalculateUpdateParams({});
+  EXPECT_TRUE(fake_system_state_.request_params()->lts_tag().empty());
+}
+
 TEST_F(UpdateAttempterTest, RollbackAllowedSetAndReset) {
-  attempter_.CalculateUpdateParams("",
-                                   "",
-                                   "",
-                                   "1234",
-                                   /*rollback_allowed=*/true,
-                                   /*rollback_data_save_requested=*/false,
-                                   /*rollback_allowed_milestones=*/4,
-                                   false,
-                                   false);
+  attempter_.CalculateUpdateParams({
+      .target_version_prefix = "1234",
+      .rollback_allowed = true,
+      .rollback_allowed_milestones = 4,
+  });
   EXPECT_TRUE(fake_system_state_.request_params()->rollback_allowed());
   EXPECT_EQ(4,
             fake_system_state_.request_params()->rollback_allowed_milestones());
 
-  attempter_.CalculateUpdateParams("",
-                                   "",
-                                   "",
-                                   "1234",
-                                   /*rollback_allowed=*/false,
-                                   /*rollback_data_save_requested=*/false,
-                                   /*rollback_allowed_milestones=*/4,
-                                   false,
-                                   false);
+  attempter_.CalculateUpdateParams({
+      .target_version_prefix = "1234",
+      .rollback_allowed_milestones = 4,
+  });
   EXPECT_FALSE(fake_system_state_.request_params()->rollback_allowed());
   EXPECT_EQ(4,
             fake_system_state_.request_params()->rollback_allowed_milestones());
 }
 
+TEST_F(UpdateAttempterTest, ChannelDowngradeNoRollback) {
+  base::ScopedTempDir tempdir;
+  ASSERT_TRUE(tempdir.CreateUniqueTempDir());
+  fake_system_state_.request_params()->set_root(tempdir.GetPath().value());
+  attempter_.CalculateUpdateParams({
+      .target_channel = "stable-channel",
+  });
+  EXPECT_FALSE(fake_system_state_.request_params()->is_powerwash_allowed());
+}
+
+TEST_F(UpdateAttempterTest, ChannelDowngradeRollback) {
+  base::ScopedTempDir tempdir;
+  ASSERT_TRUE(tempdir.CreateUniqueTempDir());
+  fake_system_state_.request_params()->set_root(tempdir.GetPath().value());
+  attempter_.CalculateUpdateParams({
+      .rollback_on_channel_downgrade = true,
+      .target_channel = "stable-channel",
+  });
+  EXPECT_TRUE(fake_system_state_.request_params()->is_powerwash_allowed());
+}
+
 TEST_F(UpdateAttempterTest, UpdateDeferredByPolicyTest) {
   // Construct an OmahaResponseHandlerAction that has processed an InstallPlan,
   // but the update is being deferred by the Policy.
@@ -1845,7 +1831,7 @@
               SetRollbackHappened(false))
       .Times(expected_reset ? 1 : 0);
   attempter_.policy_provider_ = std::move(mock_policy_provider);
-  attempter_.Update("", "", "", "", false, false, 0, false, false);
+  attempter_.Update({});
   ScheduleQuitMainLoop();
 }
 
@@ -2186,7 +2172,7 @@
         .WillOnce(Return(false));
   attempter_.policy_provider_.reset(
       new policy::PolicyProvider(std::move(device_policy)));
-  attempter_.Update("", "", "", "", false, false, 0, false, false);
+  attempter_.Update({});
 
   EXPECT_EQ(token, attempter_.omaha_request_params_->autoupdate_token());
   ScheduleQuitMainLoop();
diff --git a/update_engine.conf.chromeos b/update_engine.conf.chromeos
new file mode 100644
index 0000000..af213ad
--- /dev/null
+++ b/update_engine.conf.chromeos
@@ -0,0 +1,2 @@
+PAYLOAD_MAJOR_VERSION=2
+PAYLOAD_MINOR_VERSION=6
diff --git a/update_manager/android_things_policy.cc b/update_manager/android_things_policy.cc
index a76ea48..c4fa75a 100644
--- a/update_manager/android_things_policy.cc
+++ b/update_manager/android_things_policy.cc
@@ -58,10 +58,12 @@
   // Set the default return values.
   result->updates_enabled = true;
   result->target_channel.clear();
+  result->lts_tag.clear();
   result->target_version_prefix.clear();
   result->rollback_allowed = false;
   result->rollback_data_save_requested = false;
   result->rollback_allowed_milestones = -1;
+  result->rollback_on_channel_downgrade = false;
   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 b031dfc..ba84a41 100644
--- a/update_manager/boxed_value.cc
+++ b/update_manager/boxed_value.cc
@@ -23,6 +23,7 @@
 
 #include <base/strings/string_number_conversions.h>
 #include <base/time/time.h>
+#include <base/version.h>
 
 #include "update_engine/common/utils.h"
 #include "update_engine/connection_utils.h"
@@ -234,4 +235,30 @@
   return retval;
 }
 
+template <>
+string BoxedValue::ValuePrinter<ChannelDowngradeBehavior>(const void* value) {
+  const ChannelDowngradeBehavior* val =
+      reinterpret_cast<const ChannelDowngradeBehavior*>(value);
+  switch (*val) {
+    case ChannelDowngradeBehavior::kUnspecified:
+      return "Unspecified";
+    case ChannelDowngradeBehavior::kWaitForVersionToCatchUp:
+      return "Wait for the target channel to catch up";
+    case ChannelDowngradeBehavior::kRollback:
+      return "Roll back and powerwash on channel downgrade";
+    case ChannelDowngradeBehavior::kAllowUserToConfigure:
+      return "User decides on channel downgrade behavior";
+  }
+  NOTREACHED();
+  return "Unknown";
+}
+
+template <>
+string BoxedValue::ValuePrinter<base::Version>(const void* value) {
+  const base::Version* val = reinterpret_cast<const base::Version*>(value);
+  if (val->IsValid())
+    return val->GetString();
+  return "Unknown";
+}
+
 }  // namespace chromeos_update_manager
diff --git a/update_manager/chromeos_policy.cc b/update_manager/chromeos_policy.cc
index be5f914..4c651b7 100644
--- a/update_manager/chromeos_policy.cc
+++ b/update_manager/chromeos_policy.cc
@@ -156,6 +156,7 @@
     case ErrorCode::kUnresolvedHostRecovered:
     case ErrorCode::kNotEnoughSpace:
     case ErrorCode::kDeviceCorrupted:
+    case ErrorCode::kPackageExcludedFromUpdate:
       LOG(INFO) << "Not changing URL index or failure count due to error "
                 << chromeos_update_engine::utils::ErrorCodeToString(err_code)
                 << " (" << static_cast<int>(err_code) << ")";
@@ -216,9 +217,11 @@
   // Set the default return values.
   result->updates_enabled = true;
   result->target_channel.clear();
+  result->lts_tag.clear();
   result->target_version_prefix.clear();
   result->rollback_allowed = false;
   result->rollback_allowed_milestones = -1;
+  result->rollback_on_channel_downgrade = false;
   result->interactive = false;
 
   EnoughSlotsAbUpdatesPolicyImpl enough_slots_ab_updates_policy;
diff --git a/update_manager/chromeos_policy_unittest.cc b/update_manager/chromeos_policy_unittest.cc
index 414ac0d..996db2b 100644
--- a/update_manager/chromeos_policy_unittest.cc
+++ b/update_manager/chromeos_policy_unittest.cc
@@ -262,6 +262,8 @@
       new bool(false));
   fake_state_.device_policy_provider()->var_release_channel()->reset(
       new string("foo-channel"));
+  fake_state_.device_policy_provider()->var_release_lts_tag()->reset(
+      new string("foo-hint"));
 
   UpdateCheckParams result;
   ExpectPolicyStatus(
@@ -270,6 +272,7 @@
   EXPECT_EQ("1.2", result.target_version_prefix);
   EXPECT_EQ(5, result.rollback_allowed_milestones);
   EXPECT_EQ("foo-channel", result.target_channel);
+  EXPECT_EQ("foo-hint", result.lts_tag);
   EXPECT_FALSE(result.interactive);
 }
 
@@ -338,6 +341,26 @@
       EvalStatus::kAskMeAgainLater, &Policy::UpdateCheckAllowed, &result);
 }
 
+TEST_F(UmChromeOSPolicyTest, TestUpdateCheckIntervalTimeout) {
+  fake_state_.updater_provider()
+      ->var_test_update_check_interval_timeout()
+      ->reset(new int64_t(10));
+  fake_state_.system_provider()->var_is_official_build()->reset(
+      new bool(false));
+
+  // The first time, update should not be allowed.
+  UpdateCheckParams result;
+  ExpectPolicyStatus(
+      EvalStatus::kAskMeAgainLater, &Policy::UpdateCheckAllowed, &result);
+
+  // After moving the time forward more than the update check interval, it
+  // should now allow for update.
+  fake_clock_.SetWallclockTime(fake_clock_.GetWallclockTime() +
+                               TimeDelta::FromSeconds(11));
+  ExpectPolicyStatus(
+      EvalStatus::kSucceeded, &Policy::UpdateCheckAllowed, &result);
+}
+
 TEST_F(UmChromeOSPolicyTest,
        UpdateCheckAllowedUpdatesDisabledWhenNotEnoughSlotsAbUpdates) {
   // UpdateCheckAllowed should return false (kSucceeded) if the image booted
diff --git a/update_manager/default_policy.cc b/update_manager/default_policy.cc
index 81ab795..7ca414b 100644
--- a/update_manager/default_policy.cc
+++ b/update_manager/default_policy.cc
@@ -40,9 +40,11 @@
                                              UpdateCheckParams* result) const {
   result->updates_enabled = true;
   result->target_channel.clear();
+  result->lts_tag.clear();
   result->target_version_prefix.clear();
   result->rollback_allowed = false;
   result->rollback_allowed_milestones = -1;  // No version rolls should happen.
+  result->rollback_on_channel_downgrade = false;
   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 b68fe96..a59f2a3 100644
--- a/update_manager/device_policy_provider.h
+++ b/update_manager/device_policy_provider.h
@@ -21,6 +21,7 @@
 #include <string>
 
 #include <base/time/time.h>
+#include <base/version.h>
 #include <policy/libpolicy.h>
 
 #include "update_engine/update_manager/provider.h"
@@ -44,6 +45,8 @@
 
   virtual Variable<bool>* var_release_channel_delegated() = 0;
 
+  virtual Variable<std::string>* var_release_lts_tag() = 0;
+
   virtual Variable<bool>* var_update_disabled() = 0;
 
   virtual Variable<std::string>* var_target_version_prefix() = 0;
@@ -85,6 +88,15 @@
   virtual Variable<WeeklyTimeIntervalVector>*
   var_disallowed_time_intervals() = 0;
 
+  // Variable that determins whether we should powerwash and rollback on channel
+  // downgrade for enrolled devices.
+  virtual Variable<ChannelDowngradeBehavior>*
+  var_channel_downgrade_behavior() = 0;
+
+  // Variable that contains Chrome OS minimum required version. It contains a
+  // Chrome OS version number.
+  virtual Variable<base::Version>* var_device_minimum_version() = 0;
+
  protected:
   DevicePolicyProvider() {}
 
diff --git a/update_manager/enterprise_device_policy_impl.cc b/update_manager/enterprise_device_policy_impl.cc
index dea38ba..ce8150e 100644
--- a/update_manager/enterprise_device_policy_impl.cc
+++ b/update_manager/enterprise_device_policy_impl.cc
@@ -62,13 +62,37 @@
           ec->GetValue(system_provider->var_kiosk_required_platform_version());
       if (!kiosk_required_platform_version_p) {
         LOG(INFO) << "Kiosk app required platform version is not fetched, "
-                     "blocking update checks";
+                     "blocking update checks.";
         return EvalStatus::kAskMeAgainLater;
+      } else if (kiosk_required_platform_version_p->empty()) {
+        // The platform version could not be fetched several times. Update
+        // based on |DeviceMinimumVersion| instead (crbug.com/1048931).
+        const base::Version* device_minimum_version_p =
+            ec->GetValue(dp_provider->var_device_minimum_version());
+        const base::Version* current_version_p(
+            ec->GetValue(system_provider->var_chromeos_version()));
+        if (device_minimum_version_p && device_minimum_version_p->IsValid() &&
+            current_version_p && current_version_p->IsValid() &&
+            *current_version_p > *device_minimum_version_p) {
+          // Do not update if the current version is newer than the minimum
+          // version.
+          LOG(INFO) << "Reading kiosk app required platform version failed "
+                       "repeatedly but current version is newer than "
+                       "DeviceMinimumVersion. Blocking update checks. "
+                       "Current version: "
+                    << *current_version_p
+                    << " DeviceMinimumVersion: " << *device_minimum_version_p;
+          return EvalStatus::kAskMeAgainLater;
+        }
+        LOG(WARNING) << "Reading kiosk app required platform version failed "
+                        "repeatedly. Attempting an update without it now.";
+        // An empty string for |target_version_prefix| allows arbitrary updates.
+        result->target_version_prefix = "";
+      } else {
+        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;
       }
-
-      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 {
@@ -117,14 +141,29 @@
     if (rollback_allowed_milestones_p)
       result->rollback_allowed_milestones = *rollback_allowed_milestones_p;
 
-    // Determine whether a target channel is dictated by policy.
+    // Determine whether a target channel is dictated by policy and whether we
+    // should rollback in case that channel is more stable.
     const bool* release_channel_delegated_p =
         ec->GetValue(dp_provider->var_release_channel_delegated());
     if (release_channel_delegated_p && !(*release_channel_delegated_p)) {
       const string* release_channel_p =
           ec->GetValue(dp_provider->var_release_channel());
-      if (release_channel_p)
+      if (release_channel_p) {
         result->target_channel = *release_channel_p;
+        const ChannelDowngradeBehavior* channel_downgrade_behavior_p =
+            ec->GetValue(dp_provider->var_channel_downgrade_behavior());
+        if (channel_downgrade_behavior_p &&
+            *channel_downgrade_behavior_p ==
+                ChannelDowngradeBehavior::kRollback) {
+          result->rollback_on_channel_downgrade = true;
+        }
+      }
+    }
+
+    const string* release_lts_tag_p =
+        ec->GetValue(dp_provider->var_release_lts_tag());
+    if (release_lts_tag_p) {
+      result->lts_tag = *release_lts_tag_p;
     }
   }
   return EvalStatus::kContinue;
diff --git a/update_manager/enterprise_device_policy_impl_unittest.cc b/update_manager/enterprise_device_policy_impl_unittest.cc
new file mode 100644
index 0000000..f27715e
--- /dev/null
+++ b/update_manager/enterprise_device_policy_impl_unittest.cc
@@ -0,0 +1,161 @@
+//
+// Copyright (C) 2020 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/enterprise_device_policy_impl.h"
+
+#include <memory>
+
+#include "update_engine/update_manager/policy_test_utils.h"
+
+namespace chromeos_update_manager {
+
+class UmEnterpriseDevicePolicyImplTest : public UmPolicyTestBase {
+ protected:
+  UmEnterpriseDevicePolicyImplTest() : UmPolicyTestBase() {
+    policy_ = std::make_unique<EnterpriseDevicePolicyImpl>();
+  }
+
+  void SetUpDefaultState() override {
+    UmPolicyTestBase::SetUpDefaultState();
+
+    fake_state_.device_policy_provider()->var_device_policy_is_loaded()->reset(
+        new bool(true));
+  }
+};
+
+TEST_F(UmEnterpriseDevicePolicyImplTest, KioskAppVersionSet) {
+  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 std::string("1234.5.6"));
+
+  UpdateCheckParams result;
+  ExpectPolicyStatus(
+      EvalStatus::kContinue, &Policy::UpdateCheckAllowed, &result);
+  EXPECT_EQ(result.target_version_prefix, "1234.5.6");
+}
+
+TEST_F(UmEnterpriseDevicePolicyImplTest, KioskAppVersionUnreadableNoUpdate) {
+  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(
+      nullptr);
+
+  UpdateCheckParams result;
+  ExpectPolicyStatus(
+      EvalStatus::kAskMeAgainLater, &Policy::UpdateCheckAllowed, &result);
+}
+
+TEST_F(UmEnterpriseDevicePolicyImplTest, KioskAppVersionUnreadableUpdate) {
+  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));
+
+  // The real variable returns an empty string after several unsuccessful
+  // reading attempts. Fake this by setting it directly to empty string.
+  fake_state_.system_provider()->var_kiosk_required_platform_version()->reset(
+      new std::string(""));
+
+  UpdateCheckParams result;
+  ExpectPolicyStatus(
+      EvalStatus::kContinue, &Policy::UpdateCheckAllowed, &result);
+  EXPECT_EQ(result.target_version_prefix, "");
+}
+
+TEST_F(UmEnterpriseDevicePolicyImplTest,
+       KioskAppVersionUnreadableUpdateWithMinVersion) {
+  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));
+
+  // The real variable returns an empty string after several unsuccessful
+  // reading attempts. Fake this by setting it directly to empty string.
+  fake_state_.system_provider()->var_kiosk_required_platform_version()->reset(
+      new std::string(""));
+  // Update if the minimum version is above the current OS version.
+  fake_state_.device_policy_provider()->var_device_minimum_version()->reset(
+      new base::Version("2.0.0"));
+  fake_state_.system_provider()->var_chromeos_version()->reset(
+      new base::Version("1.0.0"));
+
+  UpdateCheckParams result;
+  ExpectPolicyStatus(
+      EvalStatus::kContinue, &Policy::UpdateCheckAllowed, &result);
+  EXPECT_EQ(result.target_version_prefix, "");
+}
+
+TEST_F(UmEnterpriseDevicePolicyImplTest,
+       KioskAppVersionUnreadableNoUpdateWithMinVersion) {
+  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));
+
+  // The real variable returns an empty string after several unsuccessful
+  // reading attempts. Fake this by setting it directly to empty string.
+  fake_state_.system_provider()->var_kiosk_required_platform_version()->reset(
+      new std::string(""));
+  // Block update if the minimum version is below the current OS version.
+  fake_state_.device_policy_provider()->var_device_minimum_version()->reset(
+      new base::Version("1.0.0"));
+  fake_state_.system_provider()->var_chromeos_version()->reset(
+      new base::Version("2.0.0"));
+
+  UpdateCheckParams result;
+  ExpectPolicyStatus(
+      EvalStatus::kAskMeAgainLater, &Policy::UpdateCheckAllowed, &result);
+}
+
+TEST_F(UmEnterpriseDevicePolicyImplTest, ChannelDowngradeBehaviorNoRollback) {
+  fake_state_.device_policy_provider()->var_release_channel_delegated()->reset(
+      new bool(false));
+  fake_state_.device_policy_provider()->var_release_channel()->reset(
+      new std::string("stable-channel"));
+
+  UpdateCheckParams result;
+  ExpectPolicyStatus(
+      EvalStatus::kContinue, &Policy::UpdateCheckAllowed, &result);
+  EXPECT_FALSE(result.rollback_on_channel_downgrade);
+}
+
+TEST_F(UmEnterpriseDevicePolicyImplTest, ChannelDowngradeBehaviorRollback) {
+  fake_state_.device_policy_provider()->var_release_channel_delegated()->reset(
+      new bool(false));
+  fake_state_.device_policy_provider()->var_release_channel()->reset(
+      new std::string("stable-channel"));
+  fake_state_.device_policy_provider()->var_channel_downgrade_behavior()->reset(
+      new ChannelDowngradeBehavior(ChannelDowngradeBehavior::kRollback));
+
+  UpdateCheckParams result;
+  ExpectPolicyStatus(
+      EvalStatus::kContinue, &Policy::UpdateCheckAllowed, &result);
+  EXPECT_TRUE(result.rollback_on_channel_downgrade);
+}
+
+}  // namespace chromeos_update_manager
diff --git a/update_manager/evaluation_context-inl.h b/update_manager/evaluation_context-inl.h
index 59d85da..82861fa 100644
--- a/update_manager/evaluation_context-inl.h
+++ b/update_manager/evaluation_context-inl.h
@@ -39,7 +39,7 @@
   std::string errmsg;
   const T* result =
       var->GetValue(RemainingTime(evaluation_monotonic_deadline_), &errmsg);
-  if (result == nullptr) {
+  if (result == nullptr && !var->IsMissingOk()) {
     LOG(WARNING) << "Error reading Variable " << var->GetName() << ": \""
                  << errmsg << "\"";
   }
diff --git a/update_manager/fake_device_policy_provider.h b/update_manager/fake_device_policy_provider.h
index 86bdef1..55d66b3 100644
--- a/update_manager/fake_device_policy_provider.h
+++ b/update_manager/fake_device_policy_provider.h
@@ -42,6 +42,10 @@
     return &var_release_channel_delegated_;
   }
 
+  FakeVariable<std::string>* var_release_lts_tag() override {
+    return &var_release_lts_tag_;
+  }
+
   FakeVariable<bool>* var_update_disabled() override {
     return &var_update_disabled_;
   }
@@ -91,6 +95,15 @@
     return &var_disallowed_time_intervals_;
   }
 
+  FakeVariable<ChannelDowngradeBehavior>* var_channel_downgrade_behavior()
+      override {
+    return &var_channel_downgrade_behavior_;
+  }
+
+  FakeVariable<base::Version>* var_device_minimum_version() override {
+    return &var_device_minimum_version_;
+  }
+
  private:
   FakeVariable<bool> var_device_policy_is_loaded_{"policy_is_loaded",
                                                   kVariableModePoll};
@@ -98,6 +111,8 @@
                                                  kVariableModePoll};
   FakeVariable<bool> var_release_channel_delegated_{"release_channel_delegated",
                                                     kVariableModePoll};
+  FakeVariable<std::string> var_release_lts_tag_{"release_lts_tag",
+                                                 kVariableModePoll};
   FakeVariable<bool> var_update_disabled_{"update_disabled", kVariableModePoll};
   FakeVariable<std::string> var_target_version_prefix_{"target_version_prefix",
                                                        kVariableModePoll};
@@ -120,6 +135,10 @@
       "auto_launched_kiosk_app_id", kVariableModePoll};
   FakeVariable<WeeklyTimeIntervalVector> var_disallowed_time_intervals_{
       "disallowed_time_intervals", kVariableModePoll};
+  FakeVariable<ChannelDowngradeBehavior> var_channel_downgrade_behavior_{
+      "channel_downgrade_behavior", kVariableModePoll};
+  FakeVariable<base::Version> var_device_minimum_version_{
+      "device_minimum_version", kVariableModePoll};
 
   DISALLOW_COPY_AND_ASSIGN(FakeDevicePolicyProvider);
 };
diff --git a/update_manager/fake_system_provider.h b/update_manager/fake_system_provider.h
index f54951b..b320c01 100644
--- a/update_manager/fake_system_provider.h
+++ b/update_manager/fake_system_provider.h
@@ -50,6 +50,10 @@
     return &var_kiosk_required_platform_version_;
   }
 
+  FakeVariable<base::Version>* var_chromeos_version() override {
+    return &var_version_;
+  }
+
  private:
   FakeVariable<bool> var_is_normal_boot_mode_{"is_normal_boot_mode",
                                               kVariableModeConst};
@@ -60,6 +64,8 @@
   FakeVariable<unsigned int> var_num_slots_{"num_slots", kVariableModePoll};
   FakeVariable<std::string> var_kiosk_required_platform_version_{
       "kiosk_required_platform_version", kVariableModePoll};
+  FakeVariable<base::Version> var_version_{"chromeos_version",
+                                           kVariableModePoll};
 
   DISALLOW_COPY_AND_ASSIGN(FakeSystemProvider);
 };
diff --git a/update_manager/fake_updater_provider.h b/update_manager/fake_updater_provider.h
index 7295765..d967f42 100644
--- a/update_manager/fake_updater_provider.h
+++ b/update_manager/fake_updater_provider.h
@@ -83,6 +83,10 @@
     return &var_update_restrictions_;
   }
 
+  FakeVariable<int64_t>* var_test_update_check_interval_timeout() override {
+    return &var_test_update_check_interval_timeout_;
+  }
+
  private:
   FakeVariable<base::Time> var_updater_started_time_{"updater_started_time",
                                                      kVariableModePoll};
@@ -108,6 +112,8 @@
       "forced_update_requested", kVariableModeAsync};
   FakeVariable<UpdateRestrictions> var_update_restrictions_{
       "update_restrictions", kVariableModePoll};
+  FakeVariable<int64_t> var_test_update_check_interval_timeout_{
+      "test_update_check_interval_timeout", kVariableModePoll};
 
   DISALLOW_COPY_AND_ASSIGN(FakeUpdaterProvider);
 };
diff --git a/update_manager/next_update_check_policy_impl.cc b/update_manager/next_update_check_policy_impl.cc
index 6f9748e..0a78718 100644
--- a/update_manager/next_update_check_policy_impl.cc
+++ b/update_manager/next_update_check_policy_impl.cc
@@ -72,6 +72,11 @@
       ec->GetValue(updater_provider->var_updater_started_time());
   POLICY_CHECK_VALUE_AND_FAIL(updater_started_time, error);
 
+  // This value is used for testing only and it will get deleted after the first
+  // time it is read.
+  const int64_t* interval_timeout =
+      ec->GetValue(updater_provider->var_test_update_check_interval_timeout());
+
   const Time* last_checked_time =
       ec->GetValue(updater_provider->var_last_checked_time());
 
@@ -83,13 +88,21 @@
   // If this is the first attempt, compute and return an initial value.
   if (last_checked_time == nullptr ||
       *last_checked_time < *updater_started_time) {
-    *next_update_check = *updater_started_time +
-                         FuzzedInterval(&prng,
-                                        constants.timeout_initial_interval,
-                                        constants.timeout_regular_fuzz);
+    TimeDelta time_diff =
+        interval_timeout == nullptr
+            ? FuzzedInterval(&prng,
+                             constants.timeout_initial_interval,
+                             constants.timeout_regular_fuzz)
+            : TimeDelta::FromSeconds(*interval_timeout);
+    *next_update_check = *updater_started_time + time_diff;
     return EvalStatus::kSucceeded;
   }
 
+  if (interval_timeout != nullptr) {
+    *next_update_check =
+        *last_checked_time + TimeDelta::FromSeconds(*interval_timeout);
+    return EvalStatus::kSucceeded;
+  }
   // Check whether the server is enforcing a poll interval; if not, this value
   // will be zero.
   const unsigned int* server_dictated_poll_interval =
diff --git a/update_manager/official_build_check_policy_impl.cc b/update_manager/official_build_check_policy_impl.cc
index 096f7bf..e80c09f 100644
--- a/update_manager/official_build_check_policy_impl.cc
+++ b/update_manager/official_build_check_policy_impl.cc
@@ -27,8 +27,16 @@
   const bool* is_official_build_p =
       ec->GetValue(state->system_provider()->var_is_official_build());
   if (is_official_build_p != nullptr && !(*is_official_build_p)) {
-    LOG(INFO) << "Unofficial build, blocking periodic update checks.";
-    return EvalStatus::kAskMeAgainLater;
+    const int64_t* interval_timeout_p = ec->GetValue(
+        state->updater_provider()->var_test_update_check_interval_timeout());
+    // The |interval_timeout | is used for testing only to test periodic
+    // update checks on unofficial images.
+    if (interval_timeout_p == nullptr) {
+      LOG(INFO) << "Unofficial build, blocking periodic update checks.";
+      return EvalStatus::kAskMeAgainLater;
+    }
+    LOG(INFO) << "Unofficial build, but periodic update check interval "
+              << "timeout is defined, so update is not blocked.";
   }
   return EvalStatus::kContinue;
 }
diff --git a/update_manager/policy.h b/update_manager/policy.h
index 844a4d0..ad6994c 100644
--- a/update_manager/policy.h
+++ b/update_manager/policy.h
@@ -43,26 +43,32 @@
 // Parameters of an update check. These parameters are determined by the
 // UpdateCheckAllowed policy.
 struct UpdateCheckParams {
-  bool updates_enabled;  // Whether the auto-updates are enabled on this build.
+  // Whether the auto-updates are enabled on this build.
+  bool updates_enabled{true};
 
   // Attributes pertaining to the case where update checks are allowed.
   //
   // 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;
+  bool rollback_allowed{false};
   // Specifies if rollbacks should attempt to preserve some system state.
-  bool rollback_data_save_requested;
+  bool rollback_data_save_requested{false};
   // 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;
+  int rollback_allowed_milestones{0};
+  // Whether a rollback with data save should be initiated on channel
+  // downgrade (e.g. beta to stable).
+  bool rollback_on_channel_downgrade{false};
   // A target channel, if so imposed by policy; otherwise, an empty string.
   std::string target_channel;
+  // Specifies if the channel hint, e.g. LTS (Long Term Support) updates.
+  std::string lts_tag;
 
   // Whether the allowed update is interactive (user-initiated) or periodic.
-  bool interactive;
+  bool interactive{false};
 };
 
 // Input arguments to UpdateCanStart.
diff --git a/update_manager/policy_utils.h b/update_manager/policy_utils.h
index 3204780..dc606f2 100644
--- a/update_manager/policy_utils.h
+++ b/update_manager/policy_utils.h
@@ -55,7 +55,6 @@
     EvalStatus status =
         (policy->*policy_method)(ec, state, error, result, args...);
     if (status != EvalStatus::kContinue) {
-      LOG(INFO) << "decision by " << policy->PolicyRequestName(policy_method);
       return status;
     }
   }
diff --git a/update_manager/real_device_policy_provider.cc b/update_manager/real_device_policy_provider.cc
index 781e2ac..0aaf20e 100644
--- a/update_manager/real_device_policy_provider.cc
+++ b/update_manager/real_device_policy_provider.cc
@@ -104,9 +104,10 @@
 }
 
 template <typename T>
-void RealDevicePolicyProvider::UpdateVariable(AsyncCopyVariable<T>* var,
-                                              bool (DevicePolicy::*getter)(T*)
-                                                  const) {
+void RealDevicePolicyProvider::UpdateVariable(
+    AsyncCopyVariable<T>* var,
+    // NOLINTNEXTLINE(readability/casting)
+    bool (DevicePolicy::*getter)(T*) const) {
   T new_value;
   if (policy_provider_->device_policy_is_loaded() &&
       (policy_provider_->GetDevicePolicy().*getter)(&new_value)) {
@@ -208,6 +209,21 @@
   return true;
 }
 
+bool RealDevicePolicyProvider::ConvertChannelDowngradeBehavior(
+    ChannelDowngradeBehavior* channel_downgrade_behavior) const {
+  int behavior;
+  if (!policy_provider_->GetDevicePolicy().GetChannelDowngradeBehavior(
+          &behavior)) {
+    return false;
+  }
+  if (behavior < static_cast<int>(ChannelDowngradeBehavior::kFirstValue) ||
+      behavior > static_cast<int>(ChannelDowngradeBehavior::kLastValue)) {
+    return false;
+  }
+  *channel_downgrade_behavior = static_cast<ChannelDowngradeBehavior>(behavior);
+  return true;
+}
+
 void RealDevicePolicyProvider::RefreshDevicePolicy() {
   if (!policy_provider_->Reload()) {
     LOG(INFO) << "No device policies/settings present.";
@@ -219,6 +235,7 @@
   UpdateVariable(&var_release_channel_, &DevicePolicy::GetReleaseChannel);
   UpdateVariable(&var_release_channel_delegated_,
                  &DevicePolicy::GetReleaseChannelDelegated);
+  UpdateVariable(&var_release_lts_tag_, &DevicePolicy::GetReleaseLtsTag);
   UpdateVariable(&var_update_disabled_, &DevicePolicy::GetUpdateDisabled);
   UpdateVariable(&var_target_version_prefix_,
                  &DevicePolicy::GetTargetVersionPrefix);
@@ -245,6 +262,10 @@
                  &DevicePolicy::GetAutoLaunchedKioskAppId);
   UpdateVariable(&var_disallowed_time_intervals_,
                  &RealDevicePolicyProvider::ConvertDisallowedTimeIntervals);
+  UpdateVariable(&var_channel_downgrade_behavior_,
+                 &RealDevicePolicyProvider::ConvertChannelDowngradeBehavior);
+  UpdateVariable(&var_device_minimum_version_,
+                 &DevicePolicy::GetHighestDeviceMinimumVersion);
 }
 
 }  // namespace chromeos_update_manager
diff --git a/update_manager/real_device_policy_provider.h b/update_manager/real_device_policy_provider.h
index 9da052d..ebda8fd 100644
--- a/update_manager/real_device_policy_provider.h
+++ b/update_manager/real_device_policy_provider.h
@@ -64,6 +64,10 @@
     return &var_release_channel_delegated_;
   }
 
+  Variable<std::string>* var_release_lts_tag() override {
+    return &var_release_lts_tag_;
+  }
+
   Variable<bool>* var_update_disabled() override {
     return &var_update_disabled_;
   }
@@ -109,6 +113,15 @@
     return &var_disallowed_time_intervals_;
   }
 
+  Variable<ChannelDowngradeBehavior>* var_channel_downgrade_behavior()
+      override {
+    return &var_channel_downgrade_behavior_;
+  }
+
+  Variable<base::Version>* var_device_minimum_version() override {
+    return &var_device_minimum_version_;
+  }
+
  private:
   FRIEND_TEST(UmRealDevicePolicyProviderTest, RefreshScheduledTest);
   FRIEND_TEST(UmRealDevicePolicyProviderTest, NonExistentDevicePolicyReloaded);
@@ -170,6 +183,11 @@
   // devices do not have an owner).
   bool ConvertHasOwner(bool* has_owner) const;
 
+  // Wrapper for |DevicePolicy::GetChannelDowngradeBehavior| that converts the
+  // result to |ChannelDowngradeBehavior|.
+  bool ConvertChannelDowngradeBehavior(
+      ChannelDowngradeBehavior* channel_downgrade_behavior) const;
+
   // Used for fetching information about the device policy.
   policy::PolicyProvider* policy_provider_;
 
@@ -191,6 +209,7 @@
   AsyncCopyVariable<std::string> var_release_channel_{"release_channel"};
   AsyncCopyVariable<bool> var_release_channel_delegated_{
       "release_channel_delegated"};
+  AsyncCopyVariable<std::string> var_release_lts_tag_{"release_lts_tag"};
   AsyncCopyVariable<bool> var_update_disabled_{"update_disabled"};
   AsyncCopyVariable<std::string> var_target_version_prefix_{
       "target_version_prefix"};
@@ -211,6 +230,10 @@
       "update_time_restrictions"};
   AsyncCopyVariable<std::string> var_auto_launched_kiosk_app_id_{
       "auto_launched_kiosk_app_id"};
+  AsyncCopyVariable<ChannelDowngradeBehavior> var_channel_downgrade_behavior_{
+      "channel_downgrade_behavior"};
+  AsyncCopyVariable<base::Version> var_device_minimum_version_{
+      "device_minimum_version"};
 
   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 84debd1..4699ad1 100644
--- a/update_manager/real_device_policy_provider_unittest.cc
+++ b/update_manager/real_device_policy_provider_unittest.cc
@@ -177,6 +177,7 @@
 
   UmTestUtils::ExpectVariableNotSet(provider_->var_release_channel());
   UmTestUtils::ExpectVariableNotSet(provider_->var_release_channel_delegated());
+  UmTestUtils::ExpectVariableNotSet(provider_->var_release_lts_tag());
   UmTestUtils::ExpectVariableNotSet(provider_->var_update_disabled());
   UmTestUtils::ExpectVariableNotSet(provider_->var_target_version_prefix());
   UmTestUtils::ExpectVariableNotSet(
@@ -194,6 +195,8 @@
   UmTestUtils::ExpectVariableNotSet(
       provider_->var_auto_launched_kiosk_app_id());
   UmTestUtils::ExpectVariableNotSet(provider_->var_disallowed_time_intervals());
+  UmTestUtils::ExpectVariableNotSet(
+      provider_->var_channel_downgrade_behavior());
 }
 
 TEST_F(UmRealDevicePolicyProviderTest, ValuesUpdated) {
@@ -376,4 +379,70 @@
       provider_->var_disallowed_time_intervals());
 }
 
+TEST_F(UmRealDevicePolicyProviderTest, ChannelDowngradeBehaviorConverted) {
+  SetUpExistentDevicePolicy();
+  EXPECT_CALL(mock_device_policy_, GetChannelDowngradeBehavior(_))
+#if USE_DBUS
+      .Times(2)
+#else
+      .Times(1)
+#endif  // USE_DBUS
+      .WillRepeatedly(DoAll(SetArgPointee<0>(static_cast<int>(
+                                ChannelDowngradeBehavior::kRollback)),
+                            Return(true)));
+  EXPECT_TRUE(provider_->Init());
+  loop_.RunOnce(false);
+
+  UmTestUtils::ExpectVariableHasValue(
+      ChannelDowngradeBehavior::kRollback,
+      provider_->var_channel_downgrade_behavior());
+}
+
+TEST_F(UmRealDevicePolicyProviderTest, ChannelDowngradeBehaviorTooSmall) {
+  SetUpExistentDevicePolicy();
+  EXPECT_CALL(mock_device_policy_, GetChannelDowngradeBehavior(_))
+#if USE_DBUS
+      .Times(2)
+#else
+      .Times(1)
+#endif  // USE_DBUS
+      .WillRepeatedly(DoAll(SetArgPointee<0>(-1), Return(true)));
+  EXPECT_TRUE(provider_->Init());
+  loop_.RunOnce(false);
+
+  UmTestUtils::ExpectVariableNotSet(
+      provider_->var_channel_downgrade_behavior());
+}
+
+TEST_F(UmRealDevicePolicyProviderTest, ChannelDowngradeBehaviorTooLarge) {
+  SetUpExistentDevicePolicy();
+  EXPECT_CALL(mock_device_policy_, GetChannelDowngradeBehavior(_))
+#if USE_DBUS
+      .Times(2)
+#else
+      .Times(1)
+#endif  // USE_DBUS
+      .WillRepeatedly(DoAll(SetArgPointee<0>(10), Return(true)));
+  EXPECT_TRUE(provider_->Init());
+  loop_.RunOnce(false);
+
+  UmTestUtils::ExpectVariableNotSet(
+      provider_->var_channel_downgrade_behavior());
+}
+
+TEST_F(UmRealDevicePolicyProviderTest, DeviceMinimumVersionPolicySet) {
+  SetUpExistentDevicePolicy();
+
+  base::Version device_minimum_version("13315.60.12");
+
+  EXPECT_CALL(mock_device_policy_, GetHighestDeviceMinimumVersion(_))
+      .WillRepeatedly(
+          DoAll(SetArgPointee<0>(device_minimum_version), Return(true)));
+  EXPECT_TRUE(provider_->Init());
+  loop_.RunOnce(false);
+
+  UmTestUtils::ExpectVariableHasValue(device_minimum_version,
+                                      provider_->var_device_minimum_version());
+}
+
 }  // namespace chromeos_update_manager
diff --git a/update_manager/real_system_provider.cc b/update_manager/real_system_provider.cc
index a900071..9b5bc02 100644
--- a/update_manager/real_system_provider.cc
+++ b/update_manager/real_system_provider.cc
@@ -24,7 +24,10 @@
 #include <kiosk-app/dbus-proxies.h>
 #endif  // USE_CHROME_KIOSK_APP
 
+#include "update_engine/common/boot_control_interface.h"
+#include "update_engine/common/hardware_interface.h"
 #include "update_engine/common/utils.h"
+#include "update_engine/omaha_request_params.h"
 #include "update_engine/update_manager/generic_variables.h"
 #include "update_engine/update_manager/variable.h"
 
@@ -64,9 +67,10 @@
     std::unique_ptr<T> result(new T());
     if (!func_.Run(result.get())) {
       if (failed_attempts_ >= kRetryPollVariableMaxRetry) {
-        // Give up on the retries, set back the desired polling interval and
-        // return the default.
+        // Give up on the retries and set back the desired polling interval.
         this->SetPollInterval(base_interval_);
+        // Release the result instead of returning a |nullptr| to indicate that
+        // the result could not be fetched.
         return result.release();
       }
       this->SetPollInterval(
@@ -96,19 +100,19 @@
 
 bool RealSystemProvider::Init() {
   var_is_normal_boot_mode_.reset(new ConstCopyVariable<bool>(
-      "is_normal_boot_mode", hardware_->IsNormalBootMode()));
+      "is_normal_boot_mode", system_state_->hardware()->IsNormalBootMode()));
 
   var_is_official_build_.reset(new ConstCopyVariable<bool>(
-      "is_official_build", hardware_->IsOfficialBuild()));
+      "is_official_build", system_state_->hardware()->IsOfficialBuild()));
 
   var_is_oobe_complete_.reset(new CallCopyVariable<bool>(
       "is_oobe_complete",
       base::Bind(&chromeos_update_engine::HardwareInterface::IsOOBEComplete,
-                 base::Unretained(hardware_),
+                 base::Unretained(system_state_->hardware()),
                  nullptr)));
 
   var_num_slots_.reset(new ConstCopyVariable<unsigned int>(
-      "num_slots", boot_control_->GetNumSlots()));
+      "num_slots", system_state_->boot_control()->GetNumSlots()));
 
   var_kiosk_required_platform_version_.reset(new RetryPollVariable<string>(
       "kiosk_required_platform_version",
@@ -116,6 +120,10 @@
       base::Bind(&RealSystemProvider::GetKioskAppRequiredPlatformVersion,
                  base::Unretained(this))));
 
+  var_chromeos_version_.reset(new ConstCopyVariable<base::Version>(
+      "chromeos_version",
+      base::Version(system_state_->request_params()->app_version())));
+
   return true;
 }
 
diff --git a/update_manager/real_system_provider.h b/update_manager/real_system_provider.h
index 114c6ea..0e68997 100644
--- a/update_manager/real_system_provider.h
+++ b/update_manager/real_system_provider.h
@@ -20,8 +20,9 @@
 #include <memory>
 #include <string>
 
-#include "update_engine/common/boot_control_interface.h"
-#include "update_engine/common/hardware_interface.h"
+#include <base/version.h>
+
+#include "update_engine/system_state.h"
 #include "update_engine/update_manager/system_provider.h"
 
 namespace org {
@@ -36,16 +37,13 @@
 class RealSystemProvider : public SystemProvider {
  public:
   RealSystemProvider(
-      chromeos_update_engine::HardwareInterface* hardware,
-      chromeos_update_engine::BootControlInterface* boot_control,
+      chromeos_update_engine::SystemState* system_state,
       org::chromium::KioskAppServiceInterfaceProxyInterface* kiosk_app_proxy)
-      : hardware_(hardware),
 #if USE_CHROME_KIOSK_APP
-        boot_control_(boot_control),
-        kiosk_app_proxy_(kiosk_app_proxy) {
+      : system_state_(system_state), kiosk_app_proxy_(kiosk_app_proxy) {
   }
 #else
-        boot_control_(boot_control) {
+      system_state_(system_state) {
   }
 #endif  // USE_CHROME_KIOSK_APP
 
@@ -72,6 +70,10 @@
     return var_kiosk_required_platform_version_.get();
   }
 
+  Variable<base::Version>* var_chromeos_version() override {
+    return var_chromeos_version_.get();
+  }
+
  private:
   bool GetKioskAppRequiredPlatformVersion(
       std::string* required_platform_version);
@@ -81,9 +83,9 @@
   std::unique_ptr<Variable<bool>> var_is_oobe_complete_;
   std::unique_ptr<Variable<unsigned int>> var_num_slots_;
   std::unique_ptr<Variable<std::string>> var_kiosk_required_platform_version_;
+  std::unique_ptr<Variable<base::Version>> var_chromeos_version_;
 
-  chromeos_update_engine::HardwareInterface* const hardware_;
-  chromeos_update_engine::BootControlInterface* const boot_control_;
+  chromeos_update_engine::SystemState* const system_state_;
 #if USE_CHROME_KIOSK_APP
   org::chromium::KioskAppServiceInterfaceProxyInterface* const kiosk_app_proxy_;
 #endif  // USE_CHROME_KIOSK_APP
diff --git a/update_manager/real_system_provider_unittest.cc b/update_manager/real_system_provider_unittest.cc
index f654f7a..9757146 100644
--- a/update_manager/real_system_provider_unittest.cc
+++ b/update_manager/real_system_provider_unittest.cc
@@ -24,6 +24,7 @@
 
 #include "update_engine/common/fake_boot_control.h"
 #include "update_engine/common/fake_hardware.h"
+#include "update_engine/fake_system_state.h"
 #include "update_engine/update_manager/umtest_utils.h"
 #if USE_CHROME_KIOSK_APP
 #include "kiosk-app/dbus-proxies.h"
@@ -54,17 +55,15 @@
         .WillByDefault(
             DoAll(SetArgPointee<0>(kRequiredPlatformVersion), Return(true)));
 
-    provider_.reset(new RealSystemProvider(
-        &fake_hardware_, &fake_boot_control_, kiosk_app_proxy_mock_.get()));
+    provider_.reset(new RealSystemProvider(&fake_system_state_,
+                                           kiosk_app_proxy_mock_.get()));
 #else
-    provider_.reset(
-        new RealSystemProvider(&fake_hardware_, &fake_boot_control_, nullptr));
+    provider_.reset(new RealSystemProvider(&fake_system_state, nullptr));
 #endif  // USE_CHROME_KIOSK_APP
     EXPECT_TRUE(provider_->Init());
   }
 
-  chromeos_update_engine::FakeHardware fake_hardware_;
-  chromeos_update_engine::FakeBootControl fake_boot_control_;
+  chromeos_update_engine::FakeSystemState fake_system_state_;
   unique_ptr<RealSystemProvider> provider_;
 
 #if USE_CHROME_KIOSK_APP
@@ -77,18 +76,29 @@
   EXPECT_NE(nullptr, provider_->var_is_official_build());
   EXPECT_NE(nullptr, provider_->var_is_oobe_complete());
   EXPECT_NE(nullptr, provider_->var_kiosk_required_platform_version());
+  EXPECT_NE(nullptr, provider_->var_chromeos_version());
 }
 
 TEST_F(UmRealSystemProviderTest, IsOOBECompleteTrue) {
-  fake_hardware_.SetIsOOBEComplete(base::Time());
+  fake_system_state_.fake_hardware()->SetIsOOBEComplete(base::Time());
   UmTestUtils::ExpectVariableHasValue(true, provider_->var_is_oobe_complete());
 }
 
 TEST_F(UmRealSystemProviderTest, IsOOBECompleteFalse) {
-  fake_hardware_.UnsetIsOOBEComplete();
+  fake_system_state_.fake_hardware()->UnsetIsOOBEComplete();
   UmTestUtils::ExpectVariableHasValue(false, provider_->var_is_oobe_complete());
 }
 
+TEST_F(UmRealSystemProviderTest, VersionFromRequestParams) {
+  fake_system_state_.request_params()->set_app_version("1.2.3");
+  // Call |Init| again to pick up the version.
+  EXPECT_TRUE(provider_->Init());
+
+  base::Version version("1.2.3");
+  UmTestUtils::ExpectVariableHasValue(version,
+                                      provider_->var_chromeos_version());
+}
+
 #if USE_CHROME_KIOSK_APP
 TEST_F(UmRealSystemProviderTest, KioskRequiredPlatformVersion) {
   UmTestUtils::ExpectVariableHasValue(
@@ -119,6 +129,22 @@
       std::string(kRequiredPlatformVersion),
       provider_->var_kiosk_required_platform_version());
 }
+
+TEST_F(UmRealSystemProviderTest, KioskRequiredPlatformVersionRepeatedFailure) {
+  // Simulate unreadable platform version. The variable should return a
+  // null pointer |kRetryPollVariableMaxRetry| times and then return an empty
+  // string to indicate that it gave up.
+  constexpr int kNumMethodCalls = 5;
+  EXPECT_CALL(*kiosk_app_proxy_mock_, GetRequiredPlatformVersion)
+      .Times(kNumMethodCalls + 1)
+      .WillRepeatedly(Return(false));
+  for (int i = 0; i < kNumMethodCalls; ++i) {
+    UmTestUtils::ExpectVariableNotSet(
+        provider_->var_kiosk_required_platform_version());
+  }
+  UmTestUtils::ExpectVariableHasValue(
+      std::string(""), provider_->var_kiosk_required_platform_version());
+}
 #else
 TEST_F(UmRealSystemProviderTest, KioskRequiredPlatformVersion) {
   UmTestUtils::ExpectVariableHasValue(
diff --git a/update_manager/real_updater_provider.cc b/update_manager/real_updater_provider.cc
index 1f9af0d..1548d57 100644
--- a/update_manager/real_updater_provider.cc
+++ b/update_manager/real_updater_provider.cc
@@ -18,6 +18,7 @@
 
 #include <inttypes.h>
 
+#include <algorithm>
 #include <string>
 
 #include <base/bind.h>
@@ -441,6 +442,46 @@
   DISALLOW_COPY_AND_ASSIGN(UpdateRestrictionsVariable);
 };
 
+// A variable class for reading timeout interval prefs value.
+class TestUpdateCheckIntervalTimeoutVariable : public Variable<int64_t> {
+ public:
+  TestUpdateCheckIntervalTimeoutVariable(
+      const string& name, chromeos_update_engine::PrefsInterface* prefs)
+      : Variable<int64_t>(name, kVariableModePoll),
+        prefs_(prefs),
+        read_count_(0) {
+    SetMissingOk();
+  }
+  ~TestUpdateCheckIntervalTimeoutVariable() = default;
+
+ private:
+  const int64_t* GetValue(TimeDelta /* timeout */,
+                          string* /* errmsg */) override {
+    auto key = chromeos_update_engine::kPrefsTestUpdateCheckIntervalTimeout;
+    int64_t result;
+    if (prefs_ && prefs_->Exists(key) && prefs_->GetInt64(key, &result)) {
+      // This specific value is used for testing only. So it should not be kept
+      // around and should be deleted after a few reads.
+      if (++read_count_ > 5)
+        prefs_->Delete(key);
+
+      // Limit the timeout interval to 10 minutes so it is not abused if it is
+      // seen on official images.
+      return new int64_t(std::min(result, static_cast<int64_t>(10 * 60)));
+    }
+    return nullptr;
+  }
+
+  chromeos_update_engine::PrefsInterface* prefs_;
+
+  // Counts how many times this variable is read. This is used to delete the
+  // underlying file defining the variable after a certain number of reads in
+  // order to prevent any abuse of this variable.
+  int read_count_;
+
+  DISALLOW_COPY_AND_ASSIGN(TestUpdateCheckIntervalTimeoutVariable);
+};
+
 // RealUpdaterProvider methods.
 
 RealUpdaterProvider::RealUpdaterProvider(SystemState* system_state)
@@ -474,6 +515,9 @@
           "server_dictated_poll_interval", system_state_)),
       var_forced_update_requested_(new ForcedUpdateRequestedVariable(
           "forced_update_requested", system_state_)),
-      var_update_restrictions_(new UpdateRestrictionsVariable(
-          "update_restrictions", system_state_)) {}
+      var_update_restrictions_(
+          new UpdateRestrictionsVariable("update_restrictions", system_state_)),
+      var_test_update_check_interval_timeout_(
+          new TestUpdateCheckIntervalTimeoutVariable(
+              "test_update_check_interval_timeout", system_state_->prefs())) {}
 }  // namespace chromeos_update_manager
diff --git a/update_manager/real_updater_provider.h b/update_manager/real_updater_provider.h
index 1b46895..0819357 100644
--- a/update_manager/real_updater_provider.h
+++ b/update_manager/real_updater_provider.h
@@ -94,6 +94,10 @@
     return var_update_restrictions_.get();
   }
 
+  Variable<int64_t>* var_test_update_check_interval_timeout() override {
+    return var_test_update_check_interval_timeout_.get();
+  }
+
  private:
   // A pointer to the update engine's system state aggregator.
   chromeos_update_engine::SystemState* system_state_;
@@ -114,6 +118,7 @@
   std::unique_ptr<Variable<unsigned int>> var_server_dictated_poll_interval_;
   std::unique_ptr<Variable<UpdateRequestStatus>> var_forced_update_requested_;
   std::unique_ptr<Variable<UpdateRestrictions>> var_update_restrictions_;
+  std::unique_ptr<Variable<int64_t>> var_test_update_check_interval_timeout_;
 
   DISALLOW_COPY_AND_ASSIGN(RealUpdaterProvider);
 };
diff --git a/update_manager/real_updater_provider_unittest.cc b/update_manager/real_updater_provider_unittest.cc
index fb7a763..06808b8 100644
--- a/update_manager/real_updater_provider_unittest.cc
+++ b/update_manager/real_updater_provider_unittest.cc
@@ -327,7 +327,7 @@
 TEST_F(UmRealUpdaterProviderTest, GetCurrChannelOkay) {
   const string kChannelName("foo-channel");
   OmahaRequestParams request_params(&fake_sys_state_);
-  request_params.Init("", "", false);
+  request_params.Init("", "", {});
   request_params.set_current_channel(kChannelName);
   fake_sys_state_.set_request_params(&request_params);
   UmTestUtils::ExpectVariableHasValue(kChannelName,
@@ -336,7 +336,7 @@
 
 TEST_F(UmRealUpdaterProviderTest, GetCurrChannelFailEmpty) {
   OmahaRequestParams request_params(&fake_sys_state_);
-  request_params.Init("", "", false);
+  request_params.Init("", "", {});
   request_params.set_current_channel("");
   fake_sys_state_.set_request_params(&request_params);
   UmTestUtils::ExpectVariableNotSet(provider_->var_curr_channel());
@@ -345,7 +345,7 @@
 TEST_F(UmRealUpdaterProviderTest, GetNewChannelOkay) {
   const string kChannelName("foo-channel");
   OmahaRequestParams request_params(&fake_sys_state_);
-  request_params.Init("", "", false);
+  request_params.Init("", "", {});
   request_params.set_target_channel(kChannelName);
   fake_sys_state_.set_request_params(&request_params);
   UmTestUtils::ExpectVariableHasValue(kChannelName,
@@ -354,7 +354,7 @@
 
 TEST_F(UmRealUpdaterProviderTest, GetNewChannelFailEmpty) {
   OmahaRequestParams request_params(&fake_sys_state_);
-  request_params.Init("", "", false);
+  request_params.Init("", "", {});
   request_params.set_target_channel("");
   fake_sys_state_.set_request_params(&request_params);
   UmTestUtils::ExpectVariableNotSet(provider_->var_new_channel());
@@ -445,4 +445,29 @@
   UmTestUtils::ExpectVariableHasValue(UpdateRestrictions::kNone,
                                       provider_->var_update_restrictions());
 }
+
+TEST_F(UmRealUpdaterProviderTest, TestUpdateCheckIntervalTimeout) {
+  UmTestUtils::ExpectVariableNotSet(
+      provider_->var_test_update_check_interval_timeout());
+  fake_prefs_.SetInt64(
+      chromeos_update_engine::kPrefsTestUpdateCheckIntervalTimeout, 1);
+  UmTestUtils::ExpectVariableHasValue(
+      static_cast<int64_t>(1),
+      provider_->var_test_update_check_interval_timeout());
+
+  // Make sure the value does not exceed a threshold of 10 minutes.
+  fake_prefs_.SetInt64(
+      chromeos_update_engine::kPrefsTestUpdateCheckIntervalTimeout, 11 * 60);
+  // The next 5 reads should return valid values.
+  for (int i = 0; i < 5; ++i)
+    UmTestUtils::ExpectVariableHasValue(
+        static_cast<int64_t>(10 * 60),
+        provider_->var_test_update_check_interval_timeout());
+
+  // Just to make sure it is not cached anywhere and deleted. The variable is
+  // allowd to be read 6 times.
+  UmTestUtils::ExpectVariableNotSet(
+      provider_->var_test_update_check_interval_timeout());
+}
+
 }  // namespace chromeos_update_manager
diff --git a/update_manager/rollback_prefs.h b/update_manager/rollback_prefs.h
index 9567701..6cbc447 100644
--- a/update_manager/rollback_prefs.h
+++ b/update_manager/rollback_prefs.h
@@ -35,6 +35,19 @@
   kMaxValue = 4
 };
 
+// Whether the device should do rollback and powerwash on channel downgrade.
+// Matches chrome_device_policy.proto's
+// |AutoUpdateSettingsProto::ChannelDowngradeBehavior|.
+enum class ChannelDowngradeBehavior {
+  kUnspecified = 0,
+  kWaitForVersionToCatchUp = 1,
+  kRollback = 2,
+  kAllowUserToConfigure = 3,
+  // These values must be kept up to date.
+  kFirstValue = kUnspecified,
+  kLastValue = kAllowUserToConfigure
+};
+
 }  // namespace chromeos_update_manager
 
 #endif  // UPDATE_ENGINE_UPDATE_MANAGER_ROLLBACK_PREFS_H_
diff --git a/update_manager/state_factory.cc b/update_manager/state_factory.cc
index 78cec6a..a0d8a63 100644
--- a/update_manager/state_factory.cc
+++ b/update_manager/state_factory.cc
@@ -69,8 +69,8 @@
   unique_ptr<FakeShillProvider> shill_provider(new FakeShillProvider());
 #endif  // USE_SHILL
   unique_ptr<RealRandomProvider> random_provider(new RealRandomProvider());
-  unique_ptr<RealSystemProvider> system_provider(new RealSystemProvider(
-      system_state->hardware(), system_state->boot_control(), kiosk_app_proxy));
+  unique_ptr<RealSystemProvider> system_provider(
+      new RealSystemProvider(system_state, kiosk_app_proxy));
 
   unique_ptr<RealTimeProvider> time_provider(new RealTimeProvider(clock));
   unique_ptr<RealUpdaterProvider> updater_provider(
diff --git a/update_manager/system_provider.h b/update_manager/system_provider.h
index 13e188b..8eb14e3 100644
--- a/update_manager/system_provider.h
+++ b/update_manager/system_provider.h
@@ -17,6 +17,10 @@
 #ifndef UPDATE_ENGINE_UPDATE_MANAGER_SYSTEM_PROVIDER_H_
 #define UPDATE_ENGINE_UPDATE_MANAGER_SYSTEM_PROVIDER_H_
 
+#include <string>
+
+#include <base/version.h>
+
 #include "update_engine/update_manager/provider.h"
 #include "update_engine/update_manager/variable.h"
 
@@ -46,6 +50,9 @@
   // with zero delay kiosk app if any.
   virtual Variable<std::string>* var_kiosk_required_platform_version() = 0;
 
+  // Chrome OS version number as provided by |ImagePropeties|.
+  virtual Variable<base::Version>* var_chromeos_version() = 0;
+
  protected:
   SystemProvider() {}
 
diff --git a/update_manager/update_manager-inl.h b/update_manager/update_manager-inl.h
index a1d172d..550642c 100644
--- a/update_manager/update_manager-inl.h
+++ b/update_manager/update_manager-inl.h
@@ -49,7 +49,6 @@
   ec->ResetEvaluation();
 
   const std::string policy_name = policy_->PolicyRequestName(policy_method);
-  LOG(INFO) << policy_name << ": START";
 
   // First try calling the actual policy.
   std::string error;
@@ -71,8 +70,6 @@
     }
   }
 
-  LOG(INFO) << policy_name << ": END";
-
   return status;
 }
 
diff --git a/update_manager/updater_provider.h b/update_manager/updater_provider.h
index 81ffb41..86af1c8 100644
--- a/update_manager/updater_provider.h
+++ b/update_manager/updater_provider.h
@@ -116,6 +116,10 @@
   // for all updates.
   virtual Variable<UpdateRestrictions>* var_update_restrictions() = 0;
 
+  // A variable that returns the number of seconds for the first update check to
+  // happen.
+  virtual Variable<int64_t>* var_test_update_check_interval_timeout() = 0;
+
  protected:
   UpdaterProvider() {}
 
diff --git a/update_manager/variable.h b/update_manager/variable.h
index 6c7d350..9ac7dae 100644
--- a/update_manager/variable.h
+++ b/update_manager/variable.h
@@ -83,6 +83,10 @@
   // variable. In other case, it returns 0.
   base::TimeDelta GetPollInterval() const { return poll_interval_; }
 
+  // Returns true, if the value for this variable is expected to be missing
+  // sometimes so we can avoid printing confusing error logs.
+  bool IsMissingOk() const { return missing_ok_; }
+
   // Adds and removes observers for value changes on the variable. This only
   // works for kVariableAsync variables since the other modes don't track value
   // changes. Adding the same observer twice has no effect.
@@ -115,6 +119,8 @@
     poll_interval_ = poll_interval;
   }
 
+  void SetMissingOk() { missing_ok_ = true; }
+
   // Calls ValueChanged on all the observers.
   void NotifyValueChanged() {
     // Fire all the observer methods from the main loop as single call. In order
@@ -140,7 +146,8 @@
       : name_(name),
         mode_(mode),
         poll_interval_(mode == kVariableModePoll ? poll_interval
-                                                 : base::TimeDelta()) {}
+                                                 : base::TimeDelta()),
+        missing_ok_(false) {}
 
   void OnValueChangedNotification() {
     // A ValueChanged() method can change the list of observers, for example
@@ -174,6 +181,9 @@
   // The list of value changes observers.
   std::list<BaseVariable::ObserverInterface*> observer_list_;
 
+  // Defines whether this variable is expected to have no value.
+  bool missing_ok_;
+
   DISALLOW_COPY_AND_ASSIGN(BaseVariable);
 };
 
diff --git a/update_metadata.proto b/update_metadata.proto
index ca59a02..452b89d 100644
--- a/update_metadata.proto
+++ b/update_metadata.proto
@@ -153,16 +153,16 @@
 //
 // All fields will be set, if this message is present.
 message ImageInfo {
-  optional string board = 1;
-  optional string key = 2;
-  optional string channel = 3;
-  optional string version = 4;
+  optional string board = 1 [deprecated = true];
+  optional string key = 2 [deprecated = true];
+  optional string channel = 3 [deprecated = true];
+  optional string version = 4 [deprecated = true];
 
   // If these values aren't present, they should be assumed to match
   // the equivalent value above. They are normally only different for
   // special image types such as nplusone images.
-  optional string build_channel = 5;
-  optional string build_version = 6;
+  optional string build_channel = 5 [deprecated = true];
+  optional string build_version = 6 [deprecated = true];
 }
 
 message InstallOperation {
@@ -173,15 +173,15 @@
     BSDIFF = 3 [deprecated = true];  // The data is a bsdiff binary diff.
 
     // On minor version 2 or newer, these operations are supported:
-    SOURCE_COPY = 4; // Copy from source to target partition
-    SOURCE_BSDIFF = 5; // Like BSDIFF, but read from source partition
+    SOURCE_COPY = 4;    // Copy from source to target partition
+    SOURCE_BSDIFF = 5;  // Like BSDIFF, but read from source partition
 
     // On minor version 3 or newer and on major version 2 or newer, these
     // operations are supported:
-    REPLACE_XZ = 8; // Replace destination extents w/ attached xz data.
+    REPLACE_XZ = 8;  // Replace destination extents w/ attached xz data.
 
     // On minor version 4 or newer, these operations are supported:
-    ZERO = 6;  // Write zeros in the destination.
+    ZERO = 6;     // Write zeros in the destination.
     DISCARD = 7;  // Discard the destination blocks, reading as undefined.
     BROTLI_BSDIFF = 10;  // Like SOURCE_BSDIFF, but compressed with brotli.
 
@@ -376,9 +376,9 @@
   optional PartitionInfo new_rootfs_info = 9 [deprecated = true];
 
   // old_image_info will only be present for delta images.
-  optional ImageInfo old_image_info = 10;
+  optional ImageInfo old_image_info = 10 [deprecated = true];
 
-  optional ImageInfo new_image_info = 11;
+  optional ImageInfo new_image_info = 11 [deprecated = true];
 
   // The minor version, also referred as "delta version", of the payload.
   // Minor version 0 is full payload, everything else is delta payload.