update_engine: Merge remote-tracking branch 'cros/upstream' into cros/master

Done with:
git merge cros/upstream --commit -s recursive

- Added EC key support and its unittests.
- Resolved a conlict on error codes. Since Android versions are not
  uploading any UMA metrics, I gave the priority to the Android version
  Since they can't be changed.
- Changed the openssl functions to get1 version (from get0) version
  because of a current issue with gale. Once the issue is resolved we
  need to change them back.
- Some remaining styling issues fixed by clang-format

BUG=b:163153182
TEST=CQ passes
TEST=unittests

Change-Id: Ib95034422b92433ce26e28336bc4806b34910d38
diff --git a/BUILD.gn b/BUILD.gn
index b7de9fc..59aa004 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}",
@@ -215,6 +218,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",
@@ -256,7 +260,7 @@
     "expat",
     "libcurl",
     "libdebugd-client",
-    "libmetrics-${libbase_ver}",
+    "libmetrics",
     "libpower_manager-client",
     "libsession_manager-client",
     "libshill-client",
@@ -416,10 +420,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 = "."
@@ -513,6 +527,7 @@
       "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",
diff --git a/client_library/client_dbus.cc b/client_library/client_dbus.cc
index 8e9a7fd..caf7bef 100644
--- a/client_library/client_dbus.cc
+++ b/client_library/client_dbus.cc
@@ -17,6 +17,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/utils.cc b/common/utils.cc
index 5d76f3f..9e1e6c5 100644
--- a/common/utils.cc
+++ b/common/utils.cc
@@ -911,6 +911,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..bcaed31 100644
--- a/common/utils.h
+++ b/common/utils.h
@@ -292,6 +292,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);
 
diff --git a/hardware_chromeos.cc b/hardware_chromeos.cc
index 807e086..cce5e84 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 {
@@ -215,8 +195,12 @@
 }
 
 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 +325,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/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..cc05648 100644
--- a/mock_update_attempter.h
+++ b/mock_update_attempter.h
@@ -30,16 +30,17 @@
  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_METHOD10(Update,
+                void(const std::string& app_version,
+                     const std::string& omaha_url,
+                     const std::string& target_channel,
+                     const std::string& lts_tag,
+                     const std::string& target_version_prefix,
+                     bool rollback_allowed,
+                     bool rollback_data_save_requested,
+                     int rollback_allowed_milestones,
+                     bool obey_proxies,
+                     bool interactive));
 
   MOCK_METHOD1(GetStatus, bool(update_engine::UpdateEngineStatus* out_status));
 
diff --git a/omaha_request_action.cc b/omaha_request_action.cc
index 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..adb95df 100644
--- a/omaha_request_action_unittest.cc
+++ b/omaha_request_action_unittest.cc
@@ -1528,6 +1528,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 +1548,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\""));
@@ -1801,6 +1804,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 +2790,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 +2804,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 +2820,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..690a4ef 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());
   }
 
diff --git a/omaha_request_builder_xml_unittest.cc b/omaha_request_builder_xml_unittest.cc
index 017acec..a804420 100644
--- a/omaha_request_builder_xml_unittest.cc
+++ b/omaha_request_builder_xml_unittest.cc
@@ -148,10 +148,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 +169,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 +191,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 +210,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 +231,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 +267,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 +289,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 +313,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.h b/omaha_request_params.h
index 76fc806..aad9290 100644
--- a/omaha_request_params.h
+++ b/omaha_request_params.h
@@ -148,6 +148,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;
   }
@@ -367,6 +371,9 @@
   //   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.
diff --git a/omaha_request_params_unittest.cc b/omaha_request_params_unittest.cc
index bfcbc32..110fb2b 100644
--- a/omaha_request_params_unittest.cc
+++ b/omaha_request_params_unittest.cc
@@ -236,6 +236,13 @@
   EXPECT_FALSE(params_.ToMoreStableChannel());
 }
 
+TEST_F(OmahaRequestParamsTest, TargetChannelHintTest) {
+  EXPECT_TRUE(params_.Init("", "", false));
+  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());
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/payload_consumer/delta_performer.cc b/payload_consumer/delta_performer.cc
index d9efc30..7375d37 100644
--- a/payload_consumer/delta_performer.cc
+++ b/payload_consumer/delta_performer.cc
@@ -1623,13 +1623,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) {
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/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_update_generator_stub.cc b/payload_consumer/partition_update_generator_stub.cc
index 8f73fbb..cfbd5e1 100644
--- a/payload_consumer/partition_update_generator_stub.cc
+++ b/payload_consumer/partition_update_generator_stub.cc
@@ -30,7 +30,7 @@
 
 namespace partition_update_generator {
 std::unique_ptr<PartitionUpdateGeneratorInterface> Create(
-    BootControlInterface* boot_control, size_t block_size)) {
+    BootControlInterface* boot_control, size_t block_size) {
   return std::make_unique<PartitionUpdateGeneratorStub>();
 }
 }  // namespace partition_update_generator
diff --git a/payload_consumer/payload_constants.cc b/payload_consumer/payload_constants.cc
index d62a0ec..663ab81 100644
--- a/payload_consumer/payload_constants.cc
+++ b/payload_consumer/payload_constants.cc
@@ -66,10 +66,6 @@
       return "PUFFDIFF";
     case InstallOperation::BROTLI_BSDIFF:
       return "BROTLI_BSDIFF";
-
-    case InstallOperation::BSDIFF:
-    case InstallOperation::MOVE:
-      NOTREACHED();
   }
   return "<unknown_op>";
 }
diff --git a/payload_consumer/payload_verifier.cc b/payload_consumer/payload_verifier.cc
index 24e337e..7fd2b8e 100644
--- a/payload_consumer/payload_verifier.cc
+++ b/payload_consumer/payload_verifier.cc
@@ -175,7 +175,10 @@
     }
 
     if (key_type == EVP_PKEY_EC) {
-      EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(public_key.get());
+      // TODO(b/158580694): Switch back to get0 version and remove manual
+      // freeing of the object once the bug is resolved or gale has been moved
+      // to informational.
+      EC_KEY* ec_key = EVP_PKEY_get1_EC_KEY(public_key.get());
       TEST_AND_RETURN_FALSE(ec_key != nullptr);
       if (ECDSA_verify(0,
                        sha256_hash_data.data(),
@@ -183,8 +186,10 @@
                        sig_data.data(),
                        sig_data.size(),
                        ec_key) == 1) {
+        EC_KEY_free(ec_key);
         return true;
       }
+      EC_KEY_free(ec_key);
     }
 
     LOG(ERROR) << "Unsupported key type " << key_type;
@@ -199,16 +204,21 @@
     const brillo::Blob& sig_data,
     const EVP_PKEY* public_key,
     brillo::Blob* out_hash_data) const {
+  // TODO(b/158580694): Switch back to get0 version and remove manual freeing of
+  // the object once the bug is resolved or gale has been moved to
+  // informational.
+  //
   // The code below executes the equivalent of:
   //
   // 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_get1_RSA(const_cast<EVP_PKEY*>(public_key));
 
   TEST_AND_RETURN_FALSE(rsa != nullptr);
   unsigned int keysize = RSA_size(rsa);
   if (sig_data.size() > 2 * keysize) {
     LOG(ERROR) << "Signature size is too big for public key size.";
+    RSA_free(rsa);
     return false;
   }
 
@@ -216,6 +226,7 @@
   brillo::Blob hash_data(keysize);
   int decrypt_size = RSA_public_decrypt(
       sig_data.size(), sig_data.data(), hash_data.data(), rsa, RSA_NO_PADDING);
+  RSA_free(rsa);
   TEST_AND_RETURN_FALSE(decrypt_size > 0 &&
                         decrypt_size <= static_cast<int>(hash_data.size()));
   hash_data.resize(decrypt_size);
diff --git a/payload_generator/generate_delta_main.cc b/payload_generator/generate_delta_main.cc
index dd41a29..5c1fb47 100644
--- a/payload_generator/generate_delta_main.cc
+++ b/payload_generator/generate_delta_main.cc
@@ -501,7 +501,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;
diff --git a/payload_generator/payload_generation_config.cc b/payload_generator/payload_generation_config.cc
index 9c5832d..35a95dc 100644
--- a/payload_generator/payload_generation_config.cc
+++ b/payload_generator/payload_generation_config.cc
@@ -266,10 +266,6 @@
 
     case InstallOperation::PUFFDIFF:
       return minor >= kPuffdiffMinorPayloadVersion;
-
-    case InstallOperation::MOVE:
-    case InstallOperation::BSDIFF:
-      NOTREACHED();
   }
   return false;
 }
diff --git a/payload_generator/payload_signer.cc b/payload_generator/payload_signer.cc
index c3264c1..9a44f94 100644
--- a/payload_generator/payload_signer.cc
+++ b/payload_generator/payload_signer.cc
@@ -309,7 +309,10 @@
   int key_type = EVP_PKEY_id(private_key.get());
   brillo::Blob signature;
   if (key_type == EVP_PKEY_RSA) {
-    RSA* rsa = EVP_PKEY_get0_RSA(private_key.get());
+    // TODO(b/158580694): Switch back to get0 version and remove manual freeing
+    // of the object once the bug is resolved or gale has been moved to
+    // informational.
+    RSA* rsa = EVP_PKEY_get1_RSA(private_key.get());
     TEST_AND_RETURN_FALSE(rsa != nullptr);
 
     brillo::Blob padded_hash = hash;
@@ -321,16 +324,20 @@
                                                  signature.data(),
                                                  rsa,
                                                  RSA_NO_PADDING);
-
     if (signature_size < 0) {
       LOG(ERROR) << "Signing hash failed: "
                  << ERR_error_string(ERR_get_error(), nullptr);
+      RSA_free(rsa);
       return false;
     }
+    RSA_free(rsa);
     TEST_AND_RETURN_FALSE(static_cast<size_t>(signature_size) ==
                           signature.size());
   } else if (key_type == EVP_PKEY_EC) {
-    EC_KEY* ec_key = EVP_PKEY_get0_EC_KEY(private_key.get());
+    // TODO(b/158580694): Switch back to get0 version and remove manual freeing
+    // of the object once the bug is resolved or gale has been moved to
+    // informational.
+    EC_KEY* ec_key = EVP_PKEY_get1_EC_KEY(private_key.get());
     TEST_AND_RETURN_FALSE(ec_key != nullptr);
 
     signature.resize(ECDSA_size(ec_key));
@@ -343,8 +350,10 @@
                    ec_key) != 1) {
       LOG(ERROR) << "Signing hash failed: "
                  << ERR_error_string(ERR_get_error(), nullptr);
+      EC_KEY_free(ec_key);
       return false;
     }
+    EC_KEY_free(ec_key);
 
     // NIST P-256
     LOG(ERROR) << "signature max size " << signature.size() << " size "
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..8667548 100644
--- a/payload_state_unittest.cc
+++ b/payload_state_unittest.cc
@@ -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/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/test_http_server.cc b/test_http_server.cc
index 1c3a2e0..cf6f10d 100644
--- a/test_http_server.cc
+++ b/test_http_server.cc
@@ -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..24562e2 100644
--- a/update_attempter.cc
+++ b/update_attempter.cc
@@ -247,6 +247,7 @@
 void UpdateAttempter::Update(const string& app_version,
                              const string& omaha_url,
                              const string& target_channel,
+                             const string& lts_tag,
                              const string& target_version_prefix,
                              bool rollback_allowed,
                              bool rollback_data_save_requested,
@@ -284,6 +285,7 @@
   if (!CalculateUpdateParams(app_version,
                              omaha_url,
                              target_channel,
+                             lts_tag,
                              target_version_prefix,
                              rollback_allowed,
                              rollback_data_save_requested,
@@ -359,6 +361,7 @@
 bool UpdateAttempter::CalculateUpdateParams(const string& app_version,
                                             const string& omaha_url,
                                             const string& target_channel,
+                                            const string& lts_tag,
                                             const string& target_version_prefix,
                                             bool rollback_allowed,
                                             bool rollback_data_save_requested,
@@ -378,6 +381,9 @@
   // Update the target version prefix.
   omaha_request_params_->set_target_version_prefix(target_version_prefix);
 
+  // Update the LTS support.
+  omaha_request_params_->set_lts_tag(lts_tag);
+
   // Set whether rollback is allowed.
   omaha_request_params_->set_rollback_allowed(rollback_allowed);
 
@@ -1103,6 +1109,7 @@
     Update(forced_app_version_,
            forced_omaha_url_,
            params.target_channel,
+           params.lts_tag,
            params.target_version_prefix,
            params.rollback_allowed,
            params.rollback_data_save_requested,
diff --git a/update_attempter.h b/update_attempter.h
index dd958f5..abd0bd4 100644
--- a/update_attempter.h
+++ b/update_attempter.h
@@ -85,6 +85,7 @@
   virtual void Update(const std::string& app_version,
                       const std::string& omaha_url,
                       const std::string& target_channel,
+                      const std::string& lts_tag,
                       const std::string& target_version_prefix,
                       bool rollback_allowed,
                       bool rollback_data_save_requested,
@@ -293,6 +294,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);
@@ -369,6 +371,7 @@
   bool CalculateUpdateParams(const std::string& app_version,
                              const std::string& omaha_url,
                              const std::string& target_channel,
+                             const std::string& lts_tag,
                              const std::string& target_version_prefix,
                              bool rollback_allowed,
                              bool rollback_data_save_requested,
diff --git a/update_attempter_unittest.cc b/update_attempter_unittest.cc
index 305dbdb..354416e 100644
--- a/update_attempter_unittest.cc
+++ b/update_attempter_unittest.cc
@@ -174,6 +174,7 @@
   void Update(const std::string& app_version,
               const std::string& omaha_url,
               const std::string& target_channel,
+              const std::string& lts_tag,
               const std::string& target_version_prefix,
               bool rollback_allowed,
               bool rollback_data_save_requested,
@@ -185,6 +186,7 @@
       UpdateAttempter::Update(app_version,
                               omaha_url,
                               target_channel,
+                              lts_tag,
                               target_version_prefix,
                               rollback_allowed,
                               rollback_data_save_requested,
@@ -425,7 +427,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("", "", "", "", "", false, false, 0, false, false);
   EXPECT_NE(old_session_id, attempter_.session_id_);
   ScheduleQuitMainLoop();
 }
@@ -796,7 +798,7 @@
     EXPECT_CALL(*processor_, StartProcessing());
   }
 
-  attempter_.Update("", "", "", "", false, false, 0, false, false);
+  attempter_.Update("", "", "", "", "", false, false, 0, false, false);
   loop_.PostTask(FROM_HERE,
                  base::Bind(&UpdateAttempterTest::UpdateTestVerify,
                             base::Unretained(this)));
@@ -996,7 +998,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("", "", "", "", "", false, false, 0, false, false);
   EXPECT_FALSE(actual_using_p2p_for_downloading_);
   EXPECT_FALSE(actual_using_p2p_for_sharing());
   ScheduleQuitMainLoop();
@@ -1018,7 +1020,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("", "", "", "", "", false, false, 0, false, false);
   EXPECT_FALSE(actual_using_p2p_for_downloading());
   EXPECT_FALSE(actual_using_p2p_for_sharing());
   ScheduleQuitMainLoop();
@@ -1041,7 +1043,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("", "", "", "", "", false, false, 0, false, false);
   EXPECT_FALSE(actual_using_p2p_for_downloading());
   EXPECT_FALSE(actual_using_p2p_for_sharing());
   ScheduleQuitMainLoop();
@@ -1063,7 +1065,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("", "", "", "", "", false, false, 0, false, false);
   EXPECT_TRUE(actual_using_p2p_for_downloading());
   EXPECT_TRUE(actual_using_p2p_for_sharing());
   ScheduleQuitMainLoop();
@@ -1090,6 +1092,7 @@
                     "",
                     "",
                     "",
+                    "",
                     false,
                     false,
                     /*rollback_allowed_milestones=*/0,
@@ -1124,7 +1127,7 @@
   attempter_.policy_provider_.reset(
       new policy::PolicyProvider(std::move(device_policy)));
 
-  attempter_.Update("", "", "", "", false, false, 0, false, false);
+  attempter_.Update("", "", "", "", "", false, false, 0, false, false);
   EXPECT_EQ(scatter_factor_in_seconds, attempter_.scatter_factor_.InSeconds());
 
   ScheduleQuitMainLoop();
@@ -1162,7 +1165,7 @@
   attempter_.policy_provider_.reset(
       new policy::PolicyProvider(std::move(device_policy)));
 
-  attempter_.Update("", "", "", "", false, false, 0, false, false);
+  attempter_.Update("", "", "", "", "", false, false, 0, false, false);
   EXPECT_EQ(scatter_factor_in_seconds, attempter_.scatter_factor_.InSeconds());
 
   // Make sure the file still exists.
@@ -1178,7 +1181,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("", "", "", "", "", false, false, 0, false, false);
   EXPECT_TRUE(fake_prefs.Exists(kPrefsUpdateCheckCount));
   EXPECT_TRUE(fake_prefs.GetInt64(kPrefsUpdateCheckCount, &new_value));
   EXPECT_EQ(initial_value, new_value);
@@ -1229,6 +1232,7 @@
                     "",
                     "",
                     "",
+                    "",
                     false,
                     false,
                     /*rollback_allowed_milestones=*/0,
@@ -1285,7 +1289,7 @@
   FakePrefs fake_prefs;
   SetUpStagingTest(kValidStagingSchedule, &fake_prefs);
 
-  attempter_.Update("", "", "", "", false, false, 0, false, false);
+  attempter_.Update("", "", "", "", "", false, false, 0, false, false);
   // Check that prefs have the correct values.
   int64_t update_count;
   EXPECT_TRUE(fake_prefs.GetInt64(kPrefsUpdateCheckCount, &update_count));
@@ -1343,7 +1347,7 @@
   SetUpStagingTest(kValidStagingSchedule, &fake_prefs);
 
   attempter_.Update(
-      "", "", "", "", false, false, 0, false, /* interactive = */ true);
+      "", "", "", "", "", false, false, 0, false, /* interactive = */ true);
   CheckStagingOff();
 
   ScheduleQuitMainLoop();
@@ -1364,7 +1368,7 @@
   SetUpStagingTest(kValidStagingSchedule, &fake_prefs);
 
   attempter_.Update(
-      "", "", "", "", false, false, 0, false, /* interactive = */ true);
+      "", "", "", "", "", false, false, 0, false, /* interactive = */ true);
   CheckStagingOff();
 
   ScheduleQuitMainLoop();
@@ -1693,20 +1697,31 @@
 
 TEST_F(UpdateAttempterTest, TargetVersionPrefixSetAndReset) {
   attempter_.CalculateUpdateParams(
-      "", "", "", "1234", false, false, 4, false, false);
+      "", "", "", "", "1234", false, false, 4, false, false);
   EXPECT_EQ("1234",
             fake_system_state_.request_params()->target_version_prefix());
 
   attempter_.CalculateUpdateParams(
-      "", "", "", "", false, 4, false, false, false);
+      "", "", "", "", "", false, 4, false, false, false);
   EXPECT_TRUE(
       fake_system_state_.request_params()->target_version_prefix().empty());
 }
 
+TEST_F(UpdateAttempterTest, TargetChannelHintSetAndReset) {
+  attempter_.CalculateUpdateParams(
+      "", "", "", "hint", "", false, false, 4, false, false);
+  EXPECT_EQ("hint", fake_system_state_.request_params()->lts_tag());
+
+  attempter_.CalculateUpdateParams(
+      "", "", "", "", "", false, 4, false, false, false);
+  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,
@@ -1720,6 +1735,7 @@
   attempter_.CalculateUpdateParams("",
                                    "",
                                    "",
+                                   "",
                                    "1234",
                                    /*rollback_allowed=*/false,
                                    /*rollback_data_save_requested=*/false,
@@ -1845,7 +1861,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("", "", "", "", "", false, false, 0, false, false);
   ScheduleQuitMainLoop();
 }
 
@@ -2186,7 +2202,7 @@
         .WillOnce(Return(false));
   attempter_.policy_provider_.reset(
       new policy::PolicyProvider(std::move(device_policy)));
-  attempter_.Update("", "", "", "", false, false, 0, false, false);
+  attempter_.Update("", "", "", "", "", false, false, 0, false, false);
 
   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..6362a73 100644
--- a/update_manager/android_things_policy.cc
+++ b/update_manager/android_things_policy.cc
@@ -58,6 +58,7 @@
   // 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;
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..85cc3ae 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,6 +217,7 @@
   // 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;
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..cc13c44 100644
--- a/update_manager/default_policy.cc
+++ b/update_manager/default_policy.cc
@@ -40,6 +40,7 @@
                                              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.
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..fed50a9 100644
--- a/update_manager/enterprise_device_policy_impl.cc
+++ b/update_manager/enterprise_device_policy_impl.cc
@@ -126,6 +126,12 @@
       if (release_channel_p)
         result->target_channel = *release_channel_p;
     }
+
+    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/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_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..9194c38 100644
--- a/update_manager/policy.h
+++ b/update_manager/policy.h
@@ -60,6 +60,8 @@
   int rollback_allowed_milestones;
   // 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;
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_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..f0804c4 100644
--- a/update_manager/real_updater_provider_unittest.cc
+++ b/update_manager/real_updater_provider_unittest.cc
@@ -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/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);
 };