Merge aosp/upstream-master into aosp/master.

Added BinderUpdateEngineClient::AttemptInstall().

Test: update_engine_unittests
Change-Id: Id6911f49d763b0d572658acb7d66857016bf6969
diff --git a/OWNERS b/OWNERS
index 0bf7587..07ee38e 100644
--- a/OWNERS
+++ b/OWNERS
@@ -7,3 +7,4 @@
 # Chromium OS maintainers:
 benchan@google.com
 ahassani@google.com
+xiaochu@google.com
diff --git a/UpdateEngine.conf b/UpdateEngine.conf
index 192e6ab..9490096 100644
--- a/UpdateEngine.conf
+++ b/UpdateEngine.conf
@@ -76,4 +76,12 @@
            send_interface="org.chromium.UpdateEngineInterface"
            send_member="GetStatus"/>
   </policy>
+  <policy user="dlcservice">
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="GetStatus"/>
+    <allow send_destination="org.chromium.UpdateEngine"
+           send_interface="org.chromium.UpdateEngineInterface"
+           send_member="AttemptInstall"/>
+  </policy>
 </busconfig>
diff --git a/boot_control_chromeos.cc b/boot_control_chromeos.cc
index 4fa1268..3dee660 100644
--- a/boot_control_chromeos.cc
+++ b/boot_control_chromeos.cc
@@ -43,6 +43,12 @@
 const char* kAndroidPartitionNameKernel = "boot";
 const char* kAndroidPartitionNameRoot = "system";
 
+const char kDlcInstallRootDirectoryEncrypted[] = "/home/chronos/dlc";
+const char kPartitionNamePrefixDlc[] = "dlc_";
+const char kPartitionNameDlcA[] = "dlc_a";
+const char kPartitionNameDlcB[] = "dlc_b";
+const char kPartitionNameDlcImage[] = "dlc.img";
+
 // Returns the currently booted rootfs partition. "/dev/sda3", for example.
 string GetBootDevice() {
   char boot_path[PATH_MAX];
@@ -143,6 +149,26 @@
 bool BootControlChromeOS::GetPartitionDevice(const string& partition_name,
                                              unsigned int slot,
                                              string* device) const {
+  // Partition name prefixed with |kPartitionNamePrefixDlc| is a DLC module.
+  if (base::StartsWith(partition_name,
+                       kPartitionNamePrefixDlc,
+                       base::CompareCase::SENSITIVE)) {
+    // Extract DLC module ID from partition_name (DLC module ID is the string
+    // after |kPartitionNamePrefixDlc| in partition_name).
+    const auto dlc_module_id =
+        partition_name.substr(strlen(kPartitionNamePrefixDlc));
+    if (dlc_module_id.empty()) {
+      LOG(ERROR) << " partition name does not contain DLC module ID:"
+                 << partition_name;
+      return false;
+    }
+    *device = base::FilePath(kDlcInstallRootDirectoryEncrypted)
+                  .Append(dlc_module_id)
+                  .Append(slot == 0 ? kPartitionNameDlcA : kPartitionNameDlcB)
+                  .Append(kPartitionNameDlcImage)
+                  .value();
+    return true;
+  }
   int partition_num = GetPartitionNumber(partition_name, slot);
   if (partition_num < 0)
     return false;
diff --git a/client_library/client_binder.cc b/client_library/client_binder.cc
index 54b33ed..5c22f84 100644
--- a/client_library/client_binder.cc
+++ b/client_library/client_binder.cc
@@ -58,6 +58,11 @@
       .isOk();
 }
 
+bool BinderUpdateEngineClient::AttemptInstall(
+    const string& omaha_url, const std::vector<string>& dlc_module_ids) {
+  return false;
+}
+
 bool BinderUpdateEngineClient::GetStatus(int64_t* out_last_checked_time,
                                          double* out_progress,
                                          UpdateStatus* out_update_status,
diff --git a/client_library/client_binder.h b/client_library/client_binder.h
index 17f2beb..b3c8940 100644
--- a/client_library/client_binder.h
+++ b/client_library/client_binder.h
@@ -47,6 +47,9 @@
                      const std::string& omaha_url,
                      bool at_user_request) override;
 
+  bool AttemptInstall(const std::string& omaha_url,
+                      const std::vector<std::string>& dlc_module_ids) override;
+
   bool GetStatus(int64_t* out_last_checked_time,
                  double* out_progress,
                  UpdateStatus* out_update_status,
diff --git a/client_library/client_dbus.cc b/client_library/client_dbus.cc
index 1072836..e0d1f64 100644
--- a/client_library/client_dbus.cc
+++ b/client_library/client_dbus.cc
@@ -20,6 +20,7 @@
 
 #include <dbus/bus.h>
 #include <update_engine/dbus-constants.h>
+#include <update_engine/proto_bindings/update_engine.pb.h>
 
 #include "update_engine/update_status_utils.h"
 
@@ -27,6 +28,7 @@
 using dbus::Bus;
 using org::chromium::UpdateEngineInterfaceProxy;
 using std::string;
+using std::vector;
 
 namespace update_engine {
 namespace internal {
@@ -55,6 +57,24 @@
       nullptr);
 }
 
+bool DBusUpdateEngineClient::AttemptInstall(
+    const string& omaha_url, const vector<string>& dlc_module_ids) {
+  // Convert parameters into protobuf.
+  chromeos_update_engine::DlcParameters dlc_parameters;
+  dlc_parameters.set_omaha_url(omaha_url);
+  for (const auto& dlc_module_id : dlc_module_ids) {
+    chromeos_update_engine::DlcInfo* dlc_info = dlc_parameters.add_dlc_infos();
+    dlc_info->set_dlc_id(dlc_module_id);
+  }
+  string dlc_request;
+  if (dlc_parameters.SerializeToString(&dlc_request)) {
+    return proxy_->AttemptInstall(dlc_request, nullptr /* brillo::ErrorPtr* */);
+  } else {
+    LOG(ERROR) << "Fail to serialize a protobuf to a string.";
+    return false;
+  }
+}
+
 bool DBusUpdateEngineClient::GetStatus(int64_t* out_last_checked_time,
                                        double* out_progress,
                                        UpdateStatus* out_update_status,
diff --git a/client_library/client_dbus.h b/client_library/client_dbus.h
index cec1665..a186d45 100644
--- a/client_library/client_dbus.h
+++ b/client_library/client_dbus.h
@@ -41,6 +41,9 @@
                      const std::string& omaha_url,
                      bool at_user_request) override;
 
+  bool AttemptInstall(const std::string& omaha_url,
+                      const std::vector<std::string>& dlc_module_ids) override;
+
   bool GetStatus(int64_t* out_last_checked_time,
                  double* out_progress,
                  UpdateStatus* out_update_status,
diff --git a/client_library/include/update_engine/client.h b/client_library/include/update_engine/client.h
index be87c76..1bc6111 100644
--- a/client_library/include/update_engine/client.h
+++ b/client_library/include/update_engine/client.h
@@ -20,6 +20,7 @@
 #include <cstdint>
 #include <memory>
 #include <string>
+#include <vector>
 
 #include "update_engine/status_update_handler.h"
 #include "update_engine/update_status.h"
@@ -47,6 +48,18 @@
                              const std::string& omaha_url,
                              bool at_user_request) = 0;
 
+  // Request the update_engine to install a list of DLC modules.
+  // |omaha_url|
+  //     Force update_engine to look for updates from the given server. Passing
+  //     empty indicates update_engine should use its default value. Note that
+  //     update_engine will ignore this parameter in production mode to avoid
+  //     pulling untrusted updates.
+  // |dlc_module_ids|
+  //     A list of DLC module IDs.
+  virtual bool AttemptInstall(
+      const std::string& omaha_url,
+      const std::vector<std::string>& dlc_module_ids) = 0;
+
   // Returns the current status of the Update Engine.
   //
   // |out_last_checked_time|
diff --git a/common/dlcservice.h b/common/dlcservice.h
new file mode 100644
index 0000000..9dae560
--- /dev/null
+++ b/common/dlcservice.h
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_DLCSERVICE_H_
+#define UPDATE_ENGINE_COMMON_DLCSERVICE_H_
+
+#include <memory>
+
+#include "update_engine/common/dlcservice_interface.h"
+
+namespace chromeos_update_engine {
+
+// This factory function creates a new DlcServiceInterface instance for the
+// current platform.
+std::unique_ptr<DlcServiceInterface> CreateDlcService();
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_DLCSERVICE_H_
diff --git a/common/dlcservice_interface.h b/common/dlcservice_interface.h
new file mode 100644
index 0000000..aa24105
--- /dev/null
+++ b/common/dlcservice_interface.h
@@ -0,0 +1,46 @@
+//
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_DLCSERVICE_INTERFACE_H_
+#define UPDATE_ENGINE_COMMON_DLCSERVICE_INTERFACE_H_
+
+#include <string>
+#include <vector>
+
+#include <base/macros.h>
+
+namespace chromeos_update_engine {
+
+// The abstract dlcservice interface defines the interaction with the
+// platform's dlcservice.
+class DlcServiceInterface {
+ public:
+  virtual ~DlcServiceInterface() = default;
+
+  // Returns true and a list of installed DLC module ids in |dlc_module_ids|.
+  // On failure it returns false.
+  virtual bool GetInstalled(std::vector<std::string>* dlc_module_ids) = 0;
+
+ protected:
+  DlcServiceInterface() = default;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DlcServiceInterface);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_DLCSERVICE_INTERFACE_H_
diff --git a/common/dlcservice_stub.cc b/common/dlcservice_stub.cc
new file mode 100644
index 0000000..c5f9306
--- /dev/null
+++ b/common/dlcservice_stub.cc
@@ -0,0 +1,36 @@
+//
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/common/dlcservice_stub.h"
+
+#include <memory>
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+std::unique_ptr<DlcServiceInterface> CreateDlcService() {
+  return std::make_unique<DlcServiceStub>();
+}
+
+bool DlcServiceStub::GetInstalled(std::vector<std::string>* dlc_module_ids) {
+  if (dlc_module_ids)
+    dlc_module_ids->clear();
+  return true;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/common/dlcservice_stub.h b/common/dlcservice_stub.h
new file mode 100644
index 0000000..4e12c11
--- /dev/null
+++ b/common/dlcservice_stub.h
@@ -0,0 +1,42 @@
+//
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_COMMON_DLCSERVICE_STUB_H_
+#define UPDATE_ENGINE_COMMON_DLCSERVICE_STUB_H_
+
+#include <string>
+#include <vector>
+
+#include "update_engine/common/dlcservice_interface.h"
+
+namespace chromeos_update_engine {
+
+// An implementation of the DlcServiceInterface that does nothing.
+class DlcServiceStub : public DlcServiceInterface {
+ public:
+  DlcServiceStub() = default;
+  ~DlcServiceStub() = default;
+
+  // BootControlInterface overrides.
+  bool GetInstalled(std::vector<std::string>* dlc_module_ids) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DlcServiceStub);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_COMMON_DLCSERVICE_STUB_H_
diff --git a/common/platform_constants_chromeos.cc b/common/platform_constants_chromeos.cc
index 3ebcf8a..3653432 100644
--- a/common/platform_constants_chromeos.cc
+++ b/common/platform_constants_chromeos.cc
@@ -22,7 +22,7 @@
 const char kOmahaDefaultProductionURL[] =
     "https://tools.google.com/service/update2";
 const char kOmahaDefaultAUTestURL[] =
-    "https://omaha.sandbox.google.com/service/update2";
+    "https://omaha-qa.sandbox.google.com/service/update2";
 const char kOmahaUpdaterID[] = "ChromeOSUpdateEngine";
 const char kOmahaPlatformName[] = "Chrome OS";
 const char kUpdatePayloadPublicKeyPath[] =
diff --git a/common/utils.h b/common/utils.h
index f7f285b..e55a6e5 100644
--- a/common/utils.h
+++ b/common/utils.h
@@ -386,7 +386,9 @@
       : processor_(processor),
         action_(action),
         code_(ErrorCode::kError),
-        should_complete_(true) {}
+        should_complete_(true) {
+    CHECK(processor_);
+  }
   ~ScopedActionCompleter() {
     if (should_complete_)
       processor_->ActionComplete(action_, code_);
diff --git a/common_service.cc b/common_service.cc
index c88d940..0f88151 100644
--- a/common_service.cc
+++ b/common_service.cc
@@ -41,6 +41,7 @@
 using brillo::ErrorPtr;
 using brillo::string_utils::ToString;
 using std::string;
+using std::vector;
 using update_engine::UpdateAttemptFlags;
 using update_engine::UpdateEngineStatus;
 
@@ -103,6 +104,18 @@
   return true;
 }
 
+bool UpdateEngineService::AttemptInstall(brillo::ErrorPtr* error,
+                                         const string& omaha_url,
+                                         const vector<string>& dlc_module_ids) {
+  if (!system_state_->update_attempter()->CheckForInstall(dlc_module_ids,
+                                                          omaha_url)) {
+    // TODO(xiaochu): support more detailed error messages.
+    LogAndSetError(error, FROM_HERE, "Could not schedule install operation.");
+    return false;
+  }
+  return true;
+}
+
 bool UpdateEngineService::AttemptRollback(ErrorPtr* error, bool in_powerwash) {
   LOG(INFO) << "Attempting rollback to non-active partitions.";
 
diff --git a/common_service.h b/common_service.h
index 824ef97..f93855d 100644
--- a/common_service.h
+++ b/common_service.h
@@ -20,6 +20,7 @@
 #include <inttypes.h>
 
 #include <string>
+#include <vector>
 
 #include <base/memory/ref_counted.h>
 #include <brillo/errors/error.h>
@@ -52,6 +53,13 @@
                      int32_t in_flags_as_int,
                      bool* out_result);
 
+  // Attempts a DLC module install operation.
+  // |omaha_url|: the URL to query for update.
+  // |dlc_module_ids|: a list of DLC module IDs.
+  bool AttemptInstall(brillo::ErrorPtr* error,
+                      const std::string& omaha_url,
+                      const std::vector<std::string>& dlc_module_ids);
+
   bool AttemptRollback(brillo::ErrorPtr* error, bool in_powerwash);
 
   // Checks if the system rollback is available by verifying if the secondary
diff --git a/common_service_unittest.cc b/common_service_unittest.cc
index d9ef567..edf90b0 100644
--- a/common_service_unittest.cc
+++ b/common_service_unittest.cc
@@ -18,6 +18,7 @@
 
 #include <gtest/gtest.h>
 #include <string>
+#include <vector>
 
 #include <brillo/errors/error.h>
 #include <policy/libpolicy.h>
@@ -28,6 +29,7 @@
 #include "update_engine/omaha_utils.h"
 
 using std::string;
+using std::vector;
 using testing::_;
 using testing::Return;
 using testing::SetArgPointee;
@@ -85,6 +87,21 @@
   EXPECT_FALSE(result);
 }
 
+TEST_F(UpdateEngineServiceTest, AttemptInstall) {
+  EXPECT_CALL(*mock_update_attempter_, CheckForInstall(_, _))
+      .WillOnce(Return(true));
+
+  EXPECT_TRUE(common_service_.AttemptInstall(&error_, "", {}));
+  EXPECT_EQ(nullptr, error_);
+}
+
+TEST_F(UpdateEngineServiceTest, AttemptInstallReturnsFalse) {
+  EXPECT_CALL(*mock_update_attempter_, CheckForInstall(_, _))
+      .WillOnce(Return(false));
+
+  EXPECT_FALSE(common_service_.AttemptInstall(&error_, "", {}));
+}
+
 // SetChannel is allowed when there's no device policy (the device is not
 // enterprise enrolled).
 TEST_F(UpdateEngineServiceTest, SetChannelWithNoPolicy) {
diff --git a/connection_manager.cc b/connection_manager.cc
index 4063f24..a048f5f 100644
--- a/connection_manager.cc
+++ b/connection_manager.cc
@@ -138,8 +138,11 @@
   if (!default_service_path.IsValid())
     return false;
   // Shill uses the "/" service path to indicate that it is not connected.
-  if (default_service_path.value() == "/")
-    return false;
+  if (default_service_path.value() == "/") {
+    *out_type = ConnectionType::kDisconnected;
+    *out_tethering = ConnectionTethering::kUnknown;
+    return true;
+  }
   TEST_AND_RETURN_FALSE(
       GetServicePathProperties(default_service_path, out_type, out_tethering));
   return true;
diff --git a/connection_manager.h b/connection_manager.h
index dc563ef..d8527a3 100644
--- a/connection_manager.h
+++ b/connection_manager.h
@@ -47,8 +47,8 @@
   bool IsAllowedConnectionTypesForUpdateSet() const override;
 
  private:
-  // Returns (via out_path) the default network path, or empty string if
-  // there's no network up. Returns true on success.
+  // Returns (via out_path) the default network path, or "/" if there's no
+  // network up. Returns true on success.
   bool GetDefaultServicePath(dbus::ObjectPath* out_path);
 
   bool GetServicePathProperties(const dbus::ObjectPath& path,
diff --git a/connection_manager_unittest.cc b/connection_manager_unittest.cc
index 85b8c57..7cd858d 100644
--- a/connection_manager_unittest.cc
+++ b/connection_manager_unittest.cc
@@ -75,6 +75,9 @@
       const char* service_type,
       const char* physical_technology,
       ConnectionType expected_type);
+
+  void TestWithServiceDisconnected(ConnectionType expected_type);
+
   void TestWithServiceTethering(
       const char* service_tethering,
       ConnectionTethering expected_tethering);
@@ -170,6 +173,18 @@
       fake_shill_proxy_->GetManagerProxy());
 }
 
+void ConnectionManagerTest::TestWithServiceDisconnected(
+    ConnectionType expected_type) {
+  SetManagerReply("/", true);
+
+  ConnectionType type;
+  ConnectionTethering tethering;
+  EXPECT_TRUE(cmut_.GetConnectionProperties(&type, &tethering));
+  EXPECT_EQ(expected_type, type);
+  testing::Mock::VerifyAndClearExpectations(
+      fake_shill_proxy_->GetManagerProxy());
+}
+
 TEST_F(ConnectionManagerTest, SimpleTest) {
   TestWithServiceType(shill::kTypeEthernet, nullptr, ConnectionType::kEthernet);
   TestWithServiceType(shill::kTypeWifi, nullptr, ConnectionType::kWifi);
@@ -203,6 +218,10 @@
   TestWithServiceType("foo", nullptr, ConnectionType::kUnknown);
 }
 
+TEST_F(ConnectionManagerTest, DisconnectTest) {
+  TestWithServiceDisconnected(ConnectionType::kDisconnected);
+}
+
 TEST_F(ConnectionManagerTest, AllowUpdatesOverEthernetTest) {
   // Updates over Ethernet are allowed even if there's no policy.
   EXPECT_TRUE(cmut_.IsUpdateAllowedOver(ConnectionType::kEthernet,
diff --git a/connection_utils.cc b/connection_utils.cc
index 9b6b526..aeb0163 100644
--- a/connection_utils.cc
+++ b/connection_utils.cc
@@ -18,6 +18,12 @@
 
 #include <shill/dbus-constants.h>
 
+namespace {
+// Not defined by shill since we don't use this outside of UE.
+constexpr char kTypeDisconnected[] = "Disconnected";
+constexpr char kTypeUnknown[] = "Unknown";
+}  // namespace
+
 namespace chromeos_update_engine {
 namespace connection_utils {
 
@@ -32,6 +38,8 @@
     return ConnectionType::kBluetooth;
   } else if (type_str == shill::kTypeCellular) {
     return ConnectionType::kCellular;
+  } else if (type_str == kTypeDisconnected) {
+    return ConnectionType::kDisconnected;
   }
   return ConnectionType::kUnknown;
 }
@@ -59,10 +67,12 @@
       return shill::kTypeBluetooth;
     case ConnectionType::kCellular:
       return shill::kTypeCellular;
+    case ConnectionType::kDisconnected:
+      return kTypeDisconnected;
     case ConnectionType::kUnknown:
-      return "Unknown";
+      return kTypeUnknown;
   }
-  return "Unknown";
+  return kTypeUnknown;
 }
 
 }  // namespace connection_utils
diff --git a/connection_utils.h b/connection_utils.h
index e385517..d5133a1 100644
--- a/connection_utils.h
+++ b/connection_utils.h
@@ -22,6 +22,7 @@
 namespace chromeos_update_engine {
 
 enum class ConnectionType {
+  kDisconnected,
   kEthernet,
   kWifi,
   kWimax,
diff --git a/dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml b/dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml
index a20f33f..f81d4ed 100644
--- a/dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml
+++ b/dbus_bindings/org.chromium.UpdateEngineInterface.dbus-xml
@@ -19,6 +19,9 @@
       <!-- See AttemptUpdateFlags enum in update_engine/dbus-constants.h. -->
       <arg type="i" name="flags" direction="in" />
     </method>
+    <method name="AttemptInstall">
+      <arg type="s" name="dlc_request" direction="in" />
+    </method>
     <method name="AttemptRollback">
       <arg type="b" name="powerwash" direction="in" />
     </method>
diff --git a/dbus_service.cc b/dbus_service.cc
index c7bc9f0..03425b6 100644
--- a/dbus_service.cc
+++ b/dbus_service.cc
@@ -16,7 +16,12 @@
 
 #include "update_engine/dbus_service.h"
 
-#include "update_engine/dbus-constants.h"
+#include <string>
+#include <vector>
+
+#include <update_engine/dbus-constants.h>
+#include <update_engine/proto_bindings/update_engine.pb.h>
+
 #include "update_engine/dbus_connection.h"
 #include "update_engine/update_status_utils.h"
 
@@ -25,6 +30,7 @@
 using brillo::ErrorPtr;
 using chromeos_update_engine::UpdateEngineService;
 using std::string;
+using std::vector;
 using update_engine::UpdateEngineStatus;
 
 DBusUpdateEngineService::DBusUpdateEngineService(SystemState* system_state)
@@ -57,6 +63,29 @@
       &result);
 }
 
+bool DBusUpdateEngineService::AttemptInstall(ErrorPtr* error,
+                                             const string& dlc_request) {
+  // Parse the raw parameters into protobuf.
+  DlcParameters dlc_parameters;
+  if (!dlc_parameters.ParseFromString(dlc_request)) {
+    *error = brillo::Error::Create(
+        FROM_HERE, "update_engine", "INTERNAL", "parameters are invalid.");
+    return false;
+  }
+  // Extract fields from the protobuf.
+  vector<string> dlc_module_ids;
+  for (const auto& dlc_info : dlc_parameters.dlc_infos()) {
+    if (dlc_info.dlc_id().empty()) {
+      *error = brillo::Error::Create(
+          FROM_HERE, "update_engine", "INTERNAL", "parameters are invalid.");
+      return false;
+    }
+    dlc_module_ids.push_back(dlc_info.dlc_id());
+  }
+  return common_->AttemptInstall(
+      error, dlc_parameters.omaha_url(), dlc_module_ids);
+}
+
 bool DBusUpdateEngineService::AttemptRollback(ErrorPtr* error,
                                               bool in_powerwash) {
   return common_->AttemptRollback(error, in_powerwash);
diff --git a/dbus_service.h b/dbus_service.h
index e461fa6..134461b 100644
--- a/dbus_service.h
+++ b/dbus_service.h
@@ -49,6 +49,9 @@
                               const std::string& in_omaha_url,
                               int32_t in_flags_as_int) override;
 
+  bool AttemptInstall(brillo::ErrorPtr* error,
+                      const std::string& dlc_request) override;
+
   bool AttemptRollback(brillo::ErrorPtr* error, bool in_powerwash) override;
 
   // Checks if the system rollback is available by verifying if the secondary
diff --git a/dlcservice_chromeos.cc b/dlcservice_chromeos.cc
new file mode 100644
index 0000000..e95f08f
--- /dev/null
+++ b/dlcservice_chromeos.cc
@@ -0,0 +1,54 @@
+//
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include "update_engine/dlcservice_chromeos.h"
+
+#include <dlcservice/dbus-proxies.h>
+#include <dlcservice/proto_bindings/dlcservice.pb.h>
+
+#include "update_engine/dbus_connection.h"
+
+using std::string;
+using std::vector;
+
+namespace chromeos_update_engine {
+
+std::unique_ptr<DlcServiceInterface> CreateDlcService() {
+  return std::make_unique<DlcServiceChromeOS>();
+}
+
+bool DlcServiceChromeOS::GetInstalled(vector<string>* dlc_module_ids) {
+  if (!dlc_module_ids)
+    return false;
+  org::chromium::DlcServiceInterfaceProxy dlcservice_proxy(
+      DBusConnection::Get()->GetDBus());
+  string dlc_module_list_str;
+  if (!dlcservice_proxy.GetInstalled(&dlc_module_list_str, nullptr)) {
+    LOG(ERROR) << "dlcservice does not return installed DLC module list.";
+    return false;
+  }
+  dlcservice::DlcModuleList dlc_module_list;
+  if (!dlc_module_list.ParseFromString(dlc_module_list_str)) {
+    LOG(ERROR) << "Errors parsing DlcModuleList protobuf.";
+    return false;
+  }
+  for (const auto& dlc_module_info : dlc_module_list.dlc_module_infos()) {
+    dlc_module_ids->emplace_back(dlc_module_info.dlc_id());
+  }
+  return true;
+}
+
+}  // namespace chromeos_update_engine
diff --git a/dlcservice_chromeos.h b/dlcservice_chromeos.h
new file mode 100644
index 0000000..8d103c1
--- /dev/null
+++ b/dlcservice_chromeos.h
@@ -0,0 +1,44 @@
+//
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#ifndef UPDATE_ENGINE_DLCSERVICE_CHROMEOS_H_
+#define UPDATE_ENGINE_DLCSERVICE_CHROMEOS_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "update_engine/common/dlcservice_interface.h"
+
+namespace chromeos_update_engine {
+
+// The Chrome OS implementation of the DlcServiceInterface. This interface
+// interacts with dlcservice via D-Bus.
+class DlcServiceChromeOS : public DlcServiceInterface {
+ public:
+  DlcServiceChromeOS() = default;
+  ~DlcServiceChromeOS() = default;
+
+  // BootControlInterface overrides.
+  bool GetInstalled(std::vector<std::string>* dlc_module_ids) override;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DlcServiceChromeOS);
+};
+
+}  // namespace chromeos_update_engine
+
+#endif  // UPDATE_ENGINE_DLCSERVICE_CHROMEOS_H_
diff --git a/fake_system_state.h b/fake_system_state.h
index 67ad3aa..627bd5c 100644
--- a/fake_system_state.h
+++ b/fake_system_state.h
@@ -100,6 +100,8 @@
     return power_manager_;
   }
 
+  inline DlcServiceInterface* dlcservice() override { return dlcservice_; }
+
   inline bool system_rebooted() override { return fake_system_rebooted_; }
 
   // Setters for the various members, can be used for overriding the default
@@ -165,6 +167,10 @@
     fake_system_rebooted_ = system_rebooted;
   }
 
+  inline void set_dlcservice(DlcServiceInterface* dlcservice) {
+    dlcservice_ = dlcservice;
+  }
+
   // Getters for the built-in default implementations. These return the actual
   // concrete type of each implementation. For additional safety, they will fail
   // whenever the requested default was overridden by a different
@@ -261,6 +267,7 @@
   P2PManager* p2p_manager_;
   chromeos_update_manager::UpdateManager* update_manager_;
   PowerManagerInterface* power_manager_{&mock_power_manager_};
+  DlcServiceInterface* dlcservice_;
 
   // Other object pointers (not preinitialized).
   const policy::DevicePolicy* device_policy_;
diff --git a/fuzz/xml.dict b/fuzz/xml.dict
new file mode 100644
index 0000000..8449cb0
--- /dev/null
+++ b/fuzz/xml.dict
@@ -0,0 +1,125 @@
+#
+# AFL dictionary for XML
+# ----------------------
+#
+# Several basic syntax elements and attributes, modeled on libxml2.
+#
+# Created by Michal Zalewski <lcamtuf@google.com>
+#
+
+attr_encoding=" encoding=\"1\""
+attr_generic=" a=\"1\""
+attr_href=" href=\"1\""
+attr_standalone=" standalone=\"no\""
+attr_version=" version=\"1\""
+attr_xml_base=" xml:base=\"1\""
+attr_xml_id=" xml:id=\"1\""
+attr_xml_lang=" xml:lang=\"1\""
+attr_xml_space=" xml:space=\"1\""
+attr_xmlns=" xmlns=\"1\""
+
+entity_builtin="&lt;"
+entity_decimal="&#1;"
+entity_external="&a;"
+entity_hex="&#x1;"
+
+# keywords
+"ANY"
+"ATTLIST"
+"CDATA"
+"DOCTYPE"
+"ELEMENT"
+"EMPTY"
+"ENTITIES"
+"ENTITY"
+"FIXED"
+"ID"
+"IDREF"
+"IDREFS"
+"IGNORE"
+"IMPLIED"
+"INCLUDE"
+"NDATA"
+"NMTOKEN"
+"NMTOKENS"
+"NOTATION"
+"PCDATA"
+"PUBLIC"
+"REQUIRED"
+"SYSTEM"
+
+# Various tag parts
+"<"
+">"
+"/>"
+"</"
+"<?"
+"?>"
+"<!"
+"!>"
+"[]"
+"]]"
+"<![CDATA["
+"<![CDATA[]]>"
+"\"\""
+"''"
+"=\"\""
+"=''"
+
+# DTD
+"<!ATTLIST"
+"<!DOCTYPE"
+"<!ELEMENT"
+"<!ENTITY"
+"<![IGNORE["
+"<![INCLUDE["
+"<!NOTATION"
+"#CDATA"
+"#FIXED"
+"#IMPLIED"
+"#PCDATA"
+"#REQUIRED"
+
+# Encodings
+"ISO-8859-1"
+"US-ASCII"
+"UTF-8"
+"UTF-16"
+"UTF-16BE"
+"UTF-16LE"
+
+# Namespaces and schemas
+"xmlns"
+"xmlns:"
+"xmlns:xhtml=\"http://www.w3.org/1999/xhtml\""
+"xmlns:xml=\"http://www.w3.org/XML/1998/namespace\""
+"xmlns:xmlns=\"http://www.w3.org/2000/xmlns\""
+
+string_col_fallback=":fallback"
+string_col_generic=":a"
+string_col_include=":include"
+string_dashes="--"
+string_parentheses="()"
+string_percent="%a"
+string_schema=":schema"
+string_ucs4="UCS-4"
+tag_close="</a>"
+tag_open="<a>"
+tag_open_close="<a />"
+
+
+"<?xml?>"
+"http://docboo"
+"http://www.w"
+"he30"
+"he2"
+"IET"
+"FDF-10"
+"aDUCS-4OPveb:"
+"a>"
+"UT"
+"xMl"
+"/usr/share/sg"
+"ha07"
+"http://www.oa"
+"cle"
diff --git a/metrics_constants.h b/metrics_constants.h
index abec2ad..eabb8fb 100644
--- a/metrics_constants.h
+++ b/metrics_constants.h
@@ -107,14 +107,15 @@
 //
 // This is used in the UpdateEngine.Attempt.ConnectionType histogram.
 enum class ConnectionType {
-  kUnknown,           // Unknown.
-  kEthernet,          // Ethernet.
-  kWifi,              // Wireless.
-  kWimax,             // WiMax.
-  kBluetooth,         // Bluetooth.
-  kCellular,          // Cellular.
-  kTetheredEthernet,  // Tethered (Ethernet).
-  kTetheredWifi,      // Tethered (Wifi).
+  kUnknown = 0,           // Unknown.
+  kEthernet = 1,          // Ethernet.
+  kWifi = 2,              // Wireless.
+  kWimax = 3,             // WiMax.
+  kBluetooth = 4,         // Bluetooth.
+  kCellular = 5,          // Cellular.
+  kTetheredEthernet = 6,  // Tethered (Ethernet).
+  kTetheredWifi = 7,      // Tethered (Wifi).
+  kDisconnected = 8,      // Disconnected.
 
   kNumConstants,
   kUnset = -1
diff --git a/metrics_reporter_android.h b/metrics_reporter_android.h
index 8a27ef6..e320c12 100644
--- a/metrics_reporter_android.h
+++ b/metrics_reporter_android.h
@@ -91,6 +91,9 @@
                                int kernel_max_rollforward_version,
                                bool kernel_max_rollforward_success) override {}
 
+  void ReportEnterpriseUpdateSeenToDownloadDays(
+      bool has_time_restriction_policy, int time_to_update_days) override {}
+
  private:
   DISALLOW_COPY_AND_ASSIGN(MetricsReporterAndroid);
 };
diff --git a/metrics_reporter_interface.h b/metrics_reporter_interface.h
index b677aaa..fce8bfd 100644
--- a/metrics_reporter_interface.h
+++ b/metrics_reporter_interface.h
@@ -226,6 +226,22 @@
   virtual void ReportKeyVersionMetrics(int kernel_min_version,
                                        int kernel_max_rollforward_version,
                                        bool kernel_max_rollforward_success) = 0;
+
+  // Helper function to report the duration between an update being seen by the
+  // client to the update being applied. Updates are not always immediately
+  // applied when seen, several enterprise policies can affect when an update
+  // would actually be downloaded and applied.
+  //
+  // This metric should only be reported for enterprise enrolled devices.
+  //
+  // The following metrics are reported from this function:
+  //   If |has_time_restriction_policy| is false:
+  //     |kMetricSuccessfulUpdateDurationFromSeenDays|
+  //   If |has_time_restriction_policy| is true:
+  //     |kMetricSuccessfulUpdateDurationFromSeenTimeRestrictedDays|
+  //
+  virtual void ReportEnterpriseUpdateSeenToDownloadDays(
+      bool has_time_restriction_policy, int time_to_update_days) = 0;
 };
 
 }  // namespace chromeos_update_engine
diff --git a/metrics_reporter_omaha.cc b/metrics_reporter_omaha.cc
index f0c6643..3ae4f4b 100644
--- a/metrics_reporter_omaha.cc
+++ b/metrics_reporter_omaha.cc
@@ -88,6 +88,10 @@
     "UpdateEngine.SuccessfulUpdate.DownloadOverheadPercentage";
 const char kMetricSuccessfulUpdateDownloadSourcesUsed[] =
     "UpdateEngine.SuccessfulUpdate.DownloadSourcesUsed";
+const char kMetricSuccessfulUpdateDurationFromSeenDays[] =
+    "UpdateEngine.SuccessfulUpdate.DurationFromSeenDays.NoTimeRestriction";
+const char kMetricSuccessfulUpdateDurationFromSeenTimeRestrictedDays[] =
+    "UpdateEngine.SuccessfulUpdate.DurationFromSeenDays.TimeRestricted";
 const char kMetricSuccessfulUpdatePayloadType[] =
     "UpdateEngine.SuccessfulUpdate.PayloadType";
 const char kMetricSuccessfulUpdatePayloadSizeMiB[] =
@@ -617,4 +621,19 @@
   metrics_lib_->SendBoolToUMA(metric, bool_value);
 }
 
+void MetricsReporterOmaha::ReportEnterpriseUpdateSeenToDownloadDays(
+    bool has_time_restriction_policy, int time_to_update_days) {
+  string metric =
+      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,
+                          1,       // min: 1 days
+                          6 * 30,  // max: 6 months (approx)
+                          50);     // num_buckets
+}
+
 }  // namespace chromeos_update_engine
diff --git a/metrics_reporter_omaha.h b/metrics_reporter_omaha.h
index 10aef86..5680dec 100644
--- a/metrics_reporter_omaha.h
+++ b/metrics_reporter_omaha.h
@@ -69,6 +69,8 @@
 extern const char kMetricSuccessfulUpdateBytesDownloadedMiB[];
 extern const char kMetricSuccessfulUpdateDownloadOverheadPercentage[];
 extern const char kMetricSuccessfulUpdateDownloadSourcesUsed[];
+extern const char kMetricSuccessfulUpdateDurationFromSeenDays[];
+extern const char kMetricSuccessfulUpdateDurationFromSeenTimeRestrictedDays[];
 extern const char kMetricSuccessfulUpdatePayloadType[];
 extern const char kMetricSuccessfulUpdatePayloadSizeMiB[];
 extern const char kMetricSuccessfulUpdateRebootCount[];
@@ -166,6 +168,9 @@
                                int kernel_max_rollforward_version,
                                bool kernel_max_rollforward_success) override;
 
+  void ReportEnterpriseUpdateSeenToDownloadDays(
+      bool has_time_restriction_policy, int time_to_update_days) override;
+
  private:
   friend class MetricsReporterOmahaTest;
 
diff --git a/metrics_reporter_omaha_unittest.cc b/metrics_reporter_omaha_unittest.cc
index 878a323..a479028 100644
--- a/metrics_reporter_omaha_unittest.cc
+++ b/metrics_reporter_omaha_unittest.cc
@@ -500,4 +500,43 @@
                                     kernel_max_rollforward_success);
 }
 
+TEST_F(MetricsReporterOmahaTest, ReportEnterpriseUpdateSeenToDownloadDays) {
+  constexpr int kDaysToUpdate = 10;
+  constexpr int kMinBucket = 1;
+  constexpr int kMaxBucket = 6 * 30;  // approximately 6 months
+  constexpr int kNumBuckets = 50;
+
+  EXPECT_CALL(*mock_metrics_lib_,
+              SendToUMA(metrics::kMetricSuccessfulUpdateDurationFromSeenDays,
+                        kDaysToUpdate,
+                        kMinBucket,
+                        kMaxBucket,
+                        kNumBuckets))
+      .Times(1);
+
+  reporter_.ReportEnterpriseUpdateSeenToDownloadDays(
+      false /* has_time_restriction_policy */, kDaysToUpdate);
+}
+
+TEST_F(MetricsReporterOmahaTest,
+       ReportEnterpriseTimeRestrictedUpdateSeenToDownloadTime) {
+  const int kDaysToUpdate = 15;
+  constexpr int kMinBucket = 1;
+  constexpr int kMaxBucket = 6 * 30;  // approximately 6 months
+  constexpr int kNumBuckets = 50;
+
+  EXPECT_CALL(
+      *mock_metrics_lib_,
+      SendToUMA(
+          metrics::kMetricSuccessfulUpdateDurationFromSeenTimeRestrictedDays,
+          kDaysToUpdate,
+          kMinBucket,
+          kMaxBucket,
+          kNumBuckets))
+      .Times(1);
+
+  reporter_.ReportEnterpriseUpdateSeenToDownloadDays(
+      true /* has_time_restriction_policy */, kDaysToUpdate);
+}
+
 }  // namespace chromeos_update_engine
diff --git a/metrics_reporter_stub.h b/metrics_reporter_stub.h
index 87023ee..25660b5 100644
--- a/metrics_reporter_stub.h
+++ b/metrics_reporter_stub.h
@@ -91,6 +91,9 @@
                                int kernel_max_rollforward_version,
                                bool kernel_max_rollforward_success) override {}
 
+  void ReportEnterpriseUpdateSeenToDownloadDays(
+      bool has_time_restriction_policy, int time_to_update_days) override {}
+
  private:
   DISALLOW_COPY_AND_ASSIGN(MetricsReporterStub);
 };
diff --git a/metrics_utils.cc b/metrics_utils.cc
index e1aa744..070626a 100644
--- a/metrics_utils.cc
+++ b/metrics_utils.cc
@@ -251,6 +251,9 @@
     case ConnectionType::kUnknown:
       return metrics::ConnectionType::kUnknown;
 
+    case ConnectionType::kDisconnected:
+      return metrics::ConnectionType::kDisconnected;
+
     case ConnectionType::kEthernet:
       if (tethering == ConnectionTethering::kConfirmed)
         return metrics::ConnectionType::kTetheredEthernet;
diff --git a/metrics_utils_unittest.cc b/metrics_utils_unittest.cc
index edf6bc3..417a72b 100644
--- a/metrics_utils_unittest.cc
+++ b/metrics_utils_unittest.cc
@@ -32,6 +32,9 @@
   EXPECT_EQ(metrics::ConnectionType::kUnknown,
             GetConnectionType(ConnectionType::kUnknown,
                               ConnectionTethering::kUnknown));
+  EXPECT_EQ(metrics::ConnectionType::kDisconnected,
+            GetConnectionType(ConnectionType::kDisconnected,
+                              ConnectionTethering::kUnknown));
   EXPECT_EQ(metrics::ConnectionType::kEthernet,
             GetConnectionType(ConnectionType::kEthernet,
                               ConnectionTethering::kUnknown));
diff --git a/mock_metrics_reporter.h b/mock_metrics_reporter.h
index c678a80..baf3a78 100644
--- a/mock_metrics_reporter.h
+++ b/mock_metrics_reporter.h
@@ -89,6 +89,9 @@
                void(int kernel_min_version,
                     int kernel_max_rollforward_version,
                     bool kernel_max_rollforward_success));
+
+  MOCK_METHOD2(ReportEnterpriseUpdateSeenToDownloadDays,
+               void(bool has_time_restriction_policy, int time_to_update_days));
 };
 
 }  // namespace chromeos_update_engine
diff --git a/mock_update_attempter.h b/mock_update_attempter.h
index 6253e3d..5df5a6b 100644
--- a/mock_update_attempter.h
+++ b/mock_update_attempter.h
@@ -18,6 +18,7 @@
 #define UPDATE_ENGINE_MOCK_UPDATE_ATTEMPTER_H_
 
 #include <string>
+#include <vector>
 
 #include "update_engine/update_attempter.h"
 
@@ -51,6 +52,10 @@
                     const std::string& omaha_url,
                     UpdateAttemptFlags flags));
 
+  MOCK_METHOD2(CheckForInstall,
+               bool(const std::vector<std::string>& dlc_module_ids,
+                    const std::string& omaha_url));
+
   MOCK_METHOD0(RefreshDevicePolicy, void(void));
 
   MOCK_CONST_METHOD0(consecutive_failed_update_checks, unsigned int(void));
diff --git a/omaha_request_action.cc b/omaha_request_action.cc
index 95dee51..9cb9b49 100644
--- a/omaha_request_action.cc
+++ b/omaha_request_action.cc
@@ -62,38 +62,62 @@
 
 namespace chromeos_update_engine {
 
-// List of custom pair tags that we interpret in the Omaha Response:
-static const char* kTagDeadline = "deadline";
-static const char* kTagDisablePayloadBackoff = "DisablePayloadBackoff";
-static const char* kTagVersion = "version";
+// List of custom attributes that we interpret in the Omaha response:
+constexpr char kAttrDeadline[] = "deadline";
+constexpr char kAttrDisableP2PForDownloading[] = "DisableP2PForDownloading";
+constexpr char kAttrDisableP2PForSharing[] = "DisableP2PForSharing";
+constexpr char kAttrDisablePayloadBackoff[] = "DisablePayloadBackoff";
+constexpr char kAttrVersion[] = "version";
 // Deprecated: "IsDelta"
-static const char* kTagIsDeltaPayload = "IsDeltaPayload";
-static const char* kTagMaxFailureCountPerUrl = "MaxFailureCountPerUrl";
-static const char* kTagMaxDaysToScatter = "MaxDaysToScatter";
+constexpr char kAttrIsDeltaPayload[] = "IsDeltaPayload";
+constexpr char kAttrMaxFailureCountPerUrl[] = "MaxFailureCountPerUrl";
+constexpr char kAttrMaxDaysToScatter[] = "MaxDaysToScatter";
 // Deprecated: "ManifestSignatureRsa"
 // Deprecated: "ManifestSize"
-static const char* kTagMetadataSignatureRsa = "MetadataSignatureRsa";
-static const char* kTagMetadataSize = "MetadataSize";
-static const char* kTagMoreInfo = "MoreInfo";
+constexpr char kAttrMetadataSignatureRsa[] = "MetadataSignatureRsa";
+constexpr char kAttrMetadataSize[] = "MetadataSize";
+constexpr char kAttrMoreInfo[] = "MoreInfo";
+constexpr char kAttrNoUpdate[] = "noupdate";
 // Deprecated: "NeedsAdmin"
-static const char* kTagPrompt = "Prompt";
-static const char* kTagDisableP2PForDownloading = "DisableP2PForDownloading";
-static const char* kTagDisableP2PForSharing = "DisableP2PForSharing";
-static const char* kTagPublicKeyRsa = "PublicKeyRsa";
-static const char* kTagPowerwash = "Powerwash";
+constexpr char kAttrPollInterval[] = "PollInterval";
+constexpr char kAttrPowerwash[] = "Powerwash";
+constexpr char kAttrPrompt[] = "Prompt";
+constexpr char kAttrPublicKeyRsa[] = "PublicKeyRsa";
 
-static const char* kOmahaUpdaterVersion = "0.1.0.0";
+// List of attributes that we interpret in the Omaha response:
+constexpr char kAttrAppId[] = "appid";
+constexpr char kAttrCodeBase[] = "codebase";
+constexpr char kAttrCohort[] = "cohort";
+constexpr char kAttrCohortHint[] = "cohorthint";
+constexpr char kAttrCohortName[] = "cohortname";
+constexpr char kAttrElapsedDays[] = "elapsed_days";
+constexpr char kAttrElapsedSeconds[] = "elapsed_seconds";
+constexpr char kAttrEvent[] = "event";
+constexpr char kAttrHashSha256[] = "hash_sha256";
+// Deprecated: "hash"; Although we still need to pass it from the server for
+// backward compatibility.
+constexpr char kAttrName[] = "name";
+// Deprecated: "sha256"; Although we still need to pass it from the server for
+// backward compatibility.
+constexpr char kAttrSize[] = "size";
+constexpr char kAttrStatus[] = "status";
+
+// List of values that we interpret in the Omaha response:
+constexpr char kValPostInstall[] = "postinstall";
+constexpr char kValNoUpdate[] = "noupdate";
+
+constexpr char kOmahaUpdaterVersion[] = "0.1.0.0";
 
 // X-Goog-Update headers.
-static const char* kXGoogleUpdateInteractivity = "X-Goog-Update-Interactivity";
-static const char* kXGoogleUpdateAppId = "X-Goog-Update-AppId";
-static const char* kXGoogleUpdateUpdater = "X-Goog-Update-Updater";
+constexpr char kXGoogleUpdateInteractivity[] = "X-Goog-Update-Interactivity";
+constexpr char kXGoogleUpdateAppId[] = "X-Goog-Update-AppId";
+constexpr char kXGoogleUpdateUpdater[] = "X-Goog-Update-Updater";
 
 // updatecheck attributes (without the underscore prefix).
-static const char* kEolAttr = "eol";
-static const char* kRollback = "rollback";
-static const char* kFirmwareVersion = "firmware_version";
-static const char* kKernelVersion = "kernel_version";
+constexpr char kAttrEol[] = "eol";
+constexpr char kAttrRollback[] = "rollback";
+constexpr char kAttrFirmwareVersion[] = "firmware_version";
+constexpr char kAttrKernelVersion[] = "kernel_version";
 
 namespace {
 
@@ -125,6 +149,7 @@
                   OmahaRequestParams* params,
                   bool ping_only,
                   bool include_ping,
+                  bool skip_updatecheck,
                   int ping_active_days,
                   int ping_roll_call_days,
                   PrefsInterface* prefs) {
@@ -133,17 +158,20 @@
     if (include_ping)
         app_body = GetPingXml(ping_active_days, ping_roll_call_days);
     if (!ping_only) {
-      app_body += "        <updatecheck";
-      if (!params->target_version_prefix().empty()) {
-        app_body += base::StringPrintf(
-            " targetversionprefix=\"%s\"",
-            XmlEncodeWithDefault(params->target_version_prefix(), "").c_str());
-        // Rollback requires target_version_prefix set.
-        if (params->rollback_allowed()) {
-          app_body += " rollback_allowed=\"true\"";
+      if (!skip_updatecheck) {
+        app_body += "        <updatecheck";
+        if (!params->target_version_prefix().empty()) {
+          app_body += base::StringPrintf(
+              " targetversionprefix=\"%s\"",
+              XmlEncodeWithDefault(params->target_version_prefix(), "")
+                  .c_str());
+          // Rollback requires target_version_prefix set.
+          if (params->rollback_allowed()) {
+            app_body += " rollback_allowed=\"true\"";
+          }
         }
+        app_body += "></updatecheck>\n";
       }
-      app_body += "></updatecheck>\n";
 
       // If this is the first update check after a reboot following a previous
       // update, generate an event containing the previous version number. If
@@ -242,12 +270,18 @@
                  const OmahaAppData& app_data,
                  bool ping_only,
                  bool include_ping,
+                 bool skip_updatecheck,
                  int ping_active_days,
                  int ping_roll_call_days,
                  int install_date_in_days,
                  SystemState* system_state) {
-  string app_body = GetAppBody(event, params, ping_only, include_ping,
-                               ping_active_days, ping_roll_call_days,
+  string app_body = GetAppBody(event,
+                               params,
+                               ping_only,
+                               include_ping,
+                               skip_updatecheck,
+                               ping_active_days,
+                               ping_roll_call_days,
                                system_state->prefs());
   string app_versions;
 
@@ -378,11 +412,13 @@
       .id = params->GetAppId(),
       .version = params->app_version(),
       .product_components = params->product_components()};
+  // Skips updatecheck for platform app in case of an install operation.
   string app_xml = GetAppXml(event,
                              params,
                              product_app,
                              ping_only,
                              include_ping,
+                             params->is_install(), /* skip_updatecheck */
                              ping_active_days,
                              ping_roll_call_days,
                              install_date_in_days,
@@ -395,6 +431,24 @@
                          system_app,
                          ping_only,
                          include_ping,
+                         false, /* skip_updatecheck */
+                         ping_active_days,
+                         ping_roll_call_days,
+                         install_date_in_days,
+                         system_state);
+  }
+  // Create APP ID according to |dlc_module_id| (sticking the current AppID to
+  // the DLC module ID with an underscode).
+  for (const auto& dlc_module_id : params->dlc_module_ids()) {
+    OmahaAppData dlc_module_app = {
+        .id = params->GetAppId() + "_" + dlc_module_id,
+        .version = params->app_version()};
+    app_xml += GetAppXml(event,
+                         params,
+                         dlc_module_app,
+                         ping_only,
+                         include_ping,
+                         false, /* skip_updatecheck */
                          ping_active_days,
                          ping_roll_call_days,
                          install_date_in_days,
@@ -488,27 +542,27 @@
 
   if (data->current_path == "/response/app") {
     OmahaParserData::App app;
-    if (attrs.find("appid") != attrs.end()) {
-      app.id = attrs["appid"];
+    if (attrs.find(kAttrAppId) != attrs.end()) {
+      app.id = attrs[kAttrAppId];
     }
-    if (attrs.find("cohort") != attrs.end()) {
+    if (attrs.find(kAttrCohort) != attrs.end()) {
       app.cohort_set = true;
-      app.cohort = attrs["cohort"];
+      app.cohort = attrs[kAttrCohort];
     }
-    if (attrs.find("cohorthint") != attrs.end()) {
+    if (attrs.find(kAttrCohortHint) != attrs.end()) {
       app.cohorthint_set = true;
-      app.cohorthint = attrs["cohorthint"];
+      app.cohorthint = attrs[kAttrCohortHint];
     }
-    if (attrs.find("cohortname") != attrs.end()) {
+    if (attrs.find(kAttrCohortName) != attrs.end()) {
       app.cohortname_set = true;
-      app.cohortname = attrs["cohortname"];
+      app.cohortname = attrs[kAttrCohortName];
     }
     data->apps.push_back(std::move(app));
   } else if (data->current_path == "/response/app/updatecheck") {
     if (!data->apps.empty())
-      data->apps.back().updatecheck_status = attrs["status"];
+      data->apps.back().updatecheck_status = attrs[kAttrStatus];
     if (data->updatecheck_poll_interval.empty())
-      data->updatecheck_poll_interval = attrs["PollInterval"];
+      data->updatecheck_poll_interval = attrs[kAttrPollInterval];
     // Omaha sends arbitrary key-value pairs as extra attributes starting with
     // an underscore.
     for (const auto& attr : attrs) {
@@ -517,27 +571,27 @@
     }
   } else if (data->current_path == "/response/daystart") {
     // Get the install-date.
-    data->daystart_elapsed_days = attrs["elapsed_days"];
-    data->daystart_elapsed_seconds = attrs["elapsed_seconds"];
+    data->daystart_elapsed_days = attrs[kAttrElapsedDays];
+    data->daystart_elapsed_seconds = attrs[kAttrElapsedSeconds];
   } else if (data->current_path == "/response/app/updatecheck/urls/url") {
     // Look at all <url> elements.
     if (!data->apps.empty())
-      data->apps.back().url_codebase.push_back(attrs["codebase"]);
+      data->apps.back().url_codebase.push_back(attrs[kAttrCodeBase]);
   } else if (data->current_path ==
              "/response/app/updatecheck/manifest/packages/package") {
     // Look at all <package> elements.
     if (!data->apps.empty())
-      data->apps.back().packages.push_back({.name = attrs["name"],
-                                            .size = attrs["size"],
-                                            .hash = attrs["hash_sha256"]});
+      data->apps.back().packages.push_back({.name = attrs[kAttrName],
+                                            .size = attrs[kAttrSize],
+                                            .hash = attrs[kAttrHashSha256]});
   } else if (data->current_path == "/response/app/updatecheck/manifest") {
     // Get the version.
     if (!data->apps.empty())
-      data->apps.back().manifest_version = attrs[kTagVersion];
+      data->apps.back().manifest_version = attrs[kAttrVersion];
   } else if (data->current_path ==
              "/response/app/updatecheck/manifest/actions/action") {
     // We only care about the postinstall action.
-    if (attrs["event"] == "postinstall" && !data->apps.empty()) {
+    if (attrs[kAttrEvent] == kValPostInstall && !data->apps.empty()) {
       data->apps.back().action_postinstall_attrs = std::move(attrs);
     }
   }
@@ -852,7 +906,8 @@
 bool ParsePackage(OmahaParserData::App* app,
                   OmahaResponse* output_object,
                   ScopedActionCompleter* completer) {
-  if (app->updatecheck_status == "noupdate") {
+  if (app->updatecheck_status.empty() ||
+      app->updatecheck_status == kValNoUpdate) {
     if (!app->packages.empty()) {
       LOG(ERROR) << "No update in this <app> but <package> is not empty.";
       completer->set_code(ErrorCode::kOmahaResponseInvalid);
@@ -872,17 +927,17 @@
   }
   LOG(INFO) << "Found " << app->url_codebase.size() << " url(s)";
   vector<string> metadata_sizes =
-      base::SplitString(app->action_postinstall_attrs[kTagMetadataSize],
+      base::SplitString(app->action_postinstall_attrs[kAttrMetadataSize],
                         ":",
                         base::TRIM_WHITESPACE,
                         base::SPLIT_WANT_ALL);
-  vector<string> metadata_signatures =
-      base::SplitString(app->action_postinstall_attrs[kTagMetadataSignatureRsa],
-                        ":",
-                        base::TRIM_WHITESPACE,
-                        base::SPLIT_WANT_ALL);
+  vector<string> metadata_signatures = base::SplitString(
+      app->action_postinstall_attrs[kAttrMetadataSignatureRsa],
+      ":",
+      base::TRIM_WHITESPACE,
+      base::SPLIT_WANT_ALL);
   vector<string> is_delta_payloads =
-      base::SplitString(app->action_postinstall_attrs[kTagIsDeltaPayload],
+      base::SplitString(app->action_postinstall_attrs[kAttrIsDeltaPayload],
                         ":",
                         base::TRIM_WHITESPACE,
                         base::SPLIT_WANT_ALL);
@@ -946,11 +1001,11 @@
 void ParseRollbackVersions(OmahaParserData* parser_data,
                            OmahaResponse* output_object) {
   utils::ParseRollbackKeyVersion(
-      parser_data->updatecheck_attrs[kFirmwareVersion],
+      parser_data->updatecheck_attrs[kAttrFirmwareVersion],
       &output_object->rollback_key_version.firmware_key,
       &output_object->rollback_key_version.firmware);
   utils::ParseRollbackKeyVersion(
-      parser_data->updatecheck_attrs[kKernelVersion],
+      parser_data->updatecheck_attrs[kAttrKernelVersion],
       &output_object->rollback_key_version.kernel_key,
       &output_object->rollback_key_version.kernel);
 }
@@ -1020,7 +1075,7 @@
   // Rollback-related updatecheck attributes.
   // Defaults to false if attribute is not present.
   output_object->is_rollback =
-      ParseBool(parser_data->updatecheck_attrs[kRollback]);
+      ParseBool(parser_data->updatecheck_attrs[kAttrRollback]);
 
   // Parses the rollback versions of the current image. If the fields do not
   // exist they default to 0xffff for the 4 key versions.
@@ -1045,22 +1100,29 @@
                                      OmahaResponse* output_object,
                                      ScopedActionCompleter* completer) {
   output_object->update_exists = false;
-  for (size_t i = 0; i < parser_data->apps.size(); i++) {
-    const string& status = parser_data->apps[i].updatecheck_status;
-    if (status == "noupdate") {
+  for (const auto& app : parser_data->apps) {
+    const string& status = app.updatecheck_status;
+    if (status == kValNoUpdate) {
       // Don't update if any app has status="noupdate".
-      LOG(INFO) << "No update for <app> " << i;
+      LOG(INFO) << "No update for <app> " << app.id;
       output_object->update_exists = false;
       break;
     } else if (status == "ok") {
-      if (parser_data->apps[i].action_postinstall_attrs["noupdate"] == "true") {
+      auto const& attr_no_update =
+          app.action_postinstall_attrs.find(kAttrNoUpdate);
+      if (attr_no_update != app.action_postinstall_attrs.end() &&
+          attr_no_update->second == "true") {
         // noupdate="true" in postinstall attributes means it's an update to
         // self, only update if there's at least one app really have update.
-        LOG(INFO) << "Update to self for <app> " << i;
+        LOG(INFO) << "Update to self for <app> " << app.id;
       } else {
-        LOG(INFO) << "Update for <app> " << i;
+        LOG(INFO) << "Update for <app> " << app.id;
         output_object->update_exists = true;
       }
+    } else if (status.empty() && params_->is_install() &&
+               params_->GetAppId() == app.id) {
+      // Skips the platform app for install operation.
+      LOG(INFO) << "No payload (and ignore) for <app> " << app.id;
     } else {
       LOG(ERROR) << "Unknown Omaha response status: " << status;
       completer->set_code(ErrorCode::kOmahaResponseInvalid);
@@ -1088,11 +1150,20 @@
       // this is the system app (this check is intentionally skipped if there is
       // no system_app_id set)
       output_object->system_version = app.manifest_version;
+    } else if (params_->is_install() &&
+               app.manifest_version != params_->app_version()) {
+      LOG(WARNING) << "An app has a different version (" << app.manifest_version
+                   << ") that is different than platform app version ("
+                   << params_->app_version() << ")";
     }
     if (!app.action_postinstall_attrs.empty() && attrs.empty()) {
       attrs = app.action_postinstall_attrs;
     }
   }
+  if (params_->is_install()) {
+    LOG(INFO) << "Use request version for Install operation.";
+    output_object->version = params_->app_version();
+  }
   if (output_object->version.empty()) {
     LOG(ERROR) << "Omaha Response does not have version in manifest!";
     completer->set_code(ErrorCode::kOmahaResponseInvalid);
@@ -1109,23 +1180,23 @@
   }
 
   // Get the optional properties one by one.
-  output_object->more_info_url = attrs[kTagMoreInfo];
-  output_object->prompt = ParseBool(attrs[kTagPrompt]);
-  output_object->deadline = attrs[kTagDeadline];
-  output_object->max_days_to_scatter = ParseInt(attrs[kTagMaxDaysToScatter]);
+  output_object->more_info_url = attrs[kAttrMoreInfo];
+  output_object->prompt = ParseBool(attrs[kAttrPrompt]);
+  output_object->deadline = attrs[kAttrDeadline];
+  output_object->max_days_to_scatter = ParseInt(attrs[kAttrMaxDaysToScatter]);
   output_object->disable_p2p_for_downloading =
-      ParseBool(attrs[kTagDisableP2PForDownloading]);
+      ParseBool(attrs[kAttrDisableP2PForDownloading]);
   output_object->disable_p2p_for_sharing =
-      ParseBool(attrs[kTagDisableP2PForSharing]);
-  output_object->public_key_rsa = attrs[kTagPublicKeyRsa];
+      ParseBool(attrs[kAttrDisableP2PForSharing]);
+  output_object->public_key_rsa = attrs[kAttrPublicKeyRsa];
 
-  string max = attrs[kTagMaxFailureCountPerUrl];
+  string max = attrs[kAttrMaxFailureCountPerUrl];
   if (!base::StringToUint(max, &output_object->max_failure_count_per_url))
     output_object->max_failure_count_per_url = kDefaultMaxFailureCountPerUrl;
 
   output_object->disable_payload_backoff =
-      ParseBool(attrs[kTagDisablePayloadBackoff]);
-  output_object->powerwash_required = ParseBool(attrs[kTagPowerwash]);
+      ParseBool(attrs[kAttrDisablePayloadBackoff]);
+  output_object->powerwash_required = ParseBool(attrs[kAttrPowerwash]);
 
   return true;
 }
@@ -1223,14 +1294,17 @@
   output_object.update_exists = true;
   SetOutputObject(output_object);
 
+  LoadOrPersistUpdateFirstSeenAtPref();
+
   ErrorCode error = ErrorCode::kSuccess;
-  if (ShouldIgnoreUpdate(&error, output_object)) {
+  if (ShouldIgnoreUpdate(output_object, &error)) {
     // No need to change output_object.update_exists here, since the value
     // has been output to the pipe.
     completer.set_code(error);
     return;
   }
 
+
   // If Omaha says to disable p2p, respect that
   if (output_object.disable_p2p_for_downloading) {
     LOG(INFO) << "Forcibly disabling use of p2p for downloading as "
@@ -1283,17 +1357,6 @@
   OmahaResponse& output_object = const_cast<OmahaResponse&>(GetOutputObject());
   PayloadStateInterface* payload_state = system_state_->payload_state();
 
-  if (system_state_->hardware()->IsOOBEEnabled() &&
-      !system_state_->hardware()->IsOOBEComplete(nullptr) &&
-      (output_object.deadline.empty() ||
-       payload_state->GetRollbackHappened()) &&
-      params_->app_version() != "ForcedUpdate") {
-    output_object.update_exists = false;
-    LOG(INFO) << "Ignoring non-critical Omaha updates until OOBE is done.";
-    completer.set_code(ErrorCode::kNonCriticalUpdateInOOBE);
-    return;
-  }
-
   if (ShouldDeferDownload(&output_object)) {
     output_object.update_exists = false;
     LOG(INFO) << "Ignoring Omaha updates as updates are deferred by policy.";
@@ -1435,47 +1498,11 @@
 OmahaRequestAction::WallClockWaitResult
 OmahaRequestAction::IsWallClockBasedWaitingSatisfied(
     OmahaResponse* output_object) {
-  Time update_first_seen_at;
-  int64_t update_first_seen_at_int;
-
-  if (system_state_->prefs()->Exists(kPrefsUpdateFirstSeenAt)) {
-    if (system_state_->prefs()->GetInt64(kPrefsUpdateFirstSeenAt,
-                                         &update_first_seen_at_int)) {
-      // Note: This timestamp could be that of ANY update we saw in the past
-      // (not necessarily this particular update we're considering to apply)
-      // but never got to apply because of some reason (e.g. stop AU policy,
-      // updates being pulled out from Omaha, changes in target version prefix,
-      // new update being rolled out, etc.). But for the purposes of scattering
-      // it doesn't matter which update the timestamp corresponds to. i.e.
-      // the clock starts ticking the first time we see an update and we're
-      // ready to apply when the random wait period is satisfied relative to
-      // that first seen timestamp.
-      update_first_seen_at = Time::FromInternalValue(update_first_seen_at_int);
-      LOG(INFO) << "Using persisted value of UpdateFirstSeenAt: "
-                << utils::ToString(update_first_seen_at);
-    } else {
-      // This seems like an unexpected error where the persisted value exists
-      // but it's not readable for some reason. Just skip scattering in this
-      // case to be safe.
-     LOG(INFO) << "Not scattering as UpdateFirstSeenAt value cannot be read";
-     return kWallClockWaitDoneAndUpdateCheckWaitNotRequired;
-    }
-  } else {
-    update_first_seen_at = system_state_->clock()->GetWallclockTime();
-    update_first_seen_at_int = update_first_seen_at.ToInternalValue();
-    if (system_state_->prefs()->SetInt64(kPrefsUpdateFirstSeenAt,
-                                         update_first_seen_at_int)) {
-      LOG(INFO) << "Persisted the new value for UpdateFirstSeenAt: "
-                << utils::ToString(update_first_seen_at);
-    } else {
-      // This seems like an unexpected error where the value cannot be
-      // persisted for some reason. Just skip scattering in this
-      // case to be safe.
-      LOG(INFO) << "Not scattering as UpdateFirstSeenAt value "
-                << utils::ToString(update_first_seen_at)
-                << " cannot be persisted";
-     return kWallClockWaitDoneAndUpdateCheckWaitNotRequired;
-    }
+  Time update_first_seen_at = LoadOrPersistUpdateFirstSeenAtPref();
+  if (update_first_seen_at == base::Time()) {
+    LOG(INFO) << "Not scattering as UpdateFirstSeenAt value cannot be read or "
+                 "persisted";
+    return kWallClockWaitDoneAndUpdateCheckWaitNotRequired;
   }
 
   TimeDelta elapsed_time =
@@ -1654,7 +1681,7 @@
 }
 
 bool OmahaRequestAction::PersistEolStatus(const map<string, string>& attrs) {
-  auto eol_attr = attrs.find(kEolAttr);
+  auto eol_attr = attrs.find(kAttrEol);
   if (eol_attr != attrs.end()) {
     return system_state_->prefs()->SetString(kPrefsOmahaEolStatus,
                                              eol_attr->second);
@@ -1727,8 +1754,8 @@
       system_state_, result, reaction, download_error_code);
 }
 
-bool OmahaRequestAction::ShouldIgnoreUpdate(
-    ErrorCode* error, const OmahaResponse& response) const {
+bool OmahaRequestAction::ShouldIgnoreUpdate(const OmahaResponse& response,
+                                            ErrorCode* error) const {
   // Note: policy decision to not update to a version we rolled back from.
   string rollback_version =
       system_state_->payload_state()->GetRollbackVersion();
@@ -1741,6 +1768,16 @@
     }
   }
 
+  if (system_state_->hardware()->IsOOBEEnabled() &&
+      !system_state_->hardware()->IsOOBEComplete(nullptr) &&
+      (response.deadline.empty() ||
+       system_state_->payload_state()->GetRollbackHappened()) &&
+      params_->app_version() != "ForcedUpdate") {
+    LOG(INFO) << "Ignoring a non-critical Omaha update before OOBE completion.";
+    *error = ErrorCode::kNonCriticalUpdateInOOBE;
+    return true;
+  }
+
   if (!IsUpdateAllowedOverCurrentConnection(error, response)) {
     LOG(INFO) << "Update is not allowed over current connection.";
     return true;
@@ -1914,4 +1951,47 @@
       min_kernel_version, max_kernel_rollforward, max_rollforward_set);
 }
 
+base::Time OmahaRequestAction::LoadOrPersistUpdateFirstSeenAtPref() const {
+  Time update_first_seen_at;
+  int64_t update_first_seen_at_int;
+  if (system_state_->prefs()->Exists(kPrefsUpdateFirstSeenAt)) {
+    if (system_state_->prefs()->GetInt64(kPrefsUpdateFirstSeenAt,
+                                         &update_first_seen_at_int)) {
+      // Note: This timestamp could be that of ANY update we saw in the past
+      // (not necessarily this particular update we're considering to apply)
+      // but never got to apply because of some reason (e.g. stop AU policy,
+      // updates being pulled out from Omaha, changes in target version prefix,
+      // new update being rolled out, etc.). But for the purposes of scattering
+      // it doesn't matter which update the timestamp corresponds to. i.e.
+      // the clock starts ticking the first time we see an update and we're
+      // ready to apply when the random wait period is satisfied relative to
+      // that first seen timestamp.
+      update_first_seen_at = Time::FromInternalValue(update_first_seen_at_int);
+      LOG(INFO) << "Using persisted value of UpdateFirstSeenAt: "
+                << utils::ToString(update_first_seen_at);
+    } else {
+      // This seems like an unexpected error where the persisted value exists
+      // but it's not readable for some reason.
+      LOG(INFO) << "UpdateFirstSeenAt value cannot be read";
+      return base::Time();
+    }
+  } else {
+    update_first_seen_at = system_state_->clock()->GetWallclockTime();
+    update_first_seen_at_int = update_first_seen_at.ToInternalValue();
+    if (system_state_->prefs()->SetInt64(kPrefsUpdateFirstSeenAt,
+                                         update_first_seen_at_int)) {
+      LOG(INFO) << "Persisted the new value for UpdateFirstSeenAt: "
+                << utils::ToString(update_first_seen_at);
+    } else {
+      // This seems like an unexpected error where the value cannot be
+      // persisted for some reason.
+      LOG(INFO) << "UpdateFirstSeenAt value "
+                << utils::ToString(update_first_seen_at)
+                << " cannot be persisted";
+      return base::Time();
+    }
+  }
+  return update_first_seen_at;
+}
+
 }  // namespace chromeos_update_engine
diff --git a/omaha_request_action.h b/omaha_request_action.h
index 1034c3f..c083abe 100644
--- a/omaha_request_action.h
+++ b/omaha_request_action.h
@@ -303,8 +303,8 @@
   void OnLookupPayloadViaP2PCompleted(const std::string& url);
 
   // Returns true if the current update should be ignored.
-  bool ShouldIgnoreUpdate(ErrorCode* error,
-                          const OmahaResponse& response) const;
+  bool ShouldIgnoreUpdate(const OmahaResponse& response,
+                          ErrorCode* error) const;
 
   // Return true if updates are allowed by user preferences.
   bool IsUpdateAllowedOverCellularByPrefs(const OmahaResponse& response) const;
@@ -322,6 +322,11 @@
   // enabled.
   void SetMaxKernelKeyVersionForRollback() const;
 
+  // Reads and returns the kPrefsUpdateFirstSeenAt pref if the pref currently
+  // exists. Otherwise saves the current wallclock time to the
+  // kPrefsUpdateFirstSeenAt pref and returns it as a base::Time object.
+  base::Time LoadOrPersistUpdateFirstSeenAtPref() const;
+
   // Global system context.
   SystemState* system_state_;
 
diff --git a/omaha_request_action_fuzzer.cc b/omaha_request_action_fuzzer.cc
new file mode 100644
index 0000000..6c2f7ca
--- /dev/null
+++ b/omaha_request_action_fuzzer.cc
@@ -0,0 +1,53 @@
+//
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#include <brillo/message_loops/fake_message_loop.h>
+
+#include "update_engine/common/mock_http_fetcher.h"
+#include "update_engine/common/test_utils.h"
+#include "update_engine/fake_system_state.h"
+#include "update_engine/omaha_request_action.h"
+
+class Environment {
+ public:
+  Environment() { logging::SetMinLogLevel(logging::LOG_FATAL); }
+};
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  static Environment env;
+  brillo::FakeMessageLoop loop(nullptr);
+  loop.SetAsCurrent();
+
+  chromeos_update_engine::FakeSystemState fake_system_state;
+  auto omaha_request_action =
+      std::make_unique<chromeos_update_engine::OmahaRequestAction>(
+          &fake_system_state,
+          nullptr,
+          std::make_unique<chromeos_update_engine::MockHttpFetcher>(
+              data, size, nullptr),
+          false);
+  auto collector_action =
+      std::make_unique<chromeos_update_engine::ObjectCollectorAction<
+          chromeos_update_engine::OmahaResponse>>();
+  BondActions(omaha_request_action.get(), collector_action.get());
+  chromeos_update_engine::ActionProcessor action_processor;
+  action_processor.EnqueueAction(std::move(omaha_request_action));
+  action_processor.EnqueueAction(std::move(collector_action));
+  action_processor.StartProcessing();
+
+  loop.Run();
+  return 0;
+}
diff --git a/omaha_request_action_unittest.cc b/omaha_request_action_unittest.cc
index 019b723..9a5cd10 100644
--- a/omaha_request_action_unittest.cc
+++ b/omaha_request_action_unittest.cc
@@ -77,8 +77,10 @@
               "Don't change the value of kRollforward infinity unless its "
               "size has been changed in firmware.");
 
+const char kCurrentVersion[] = "0.1.0.0";
 const char kTestAppId[] = "test-app-id";
 const char kTestAppId2[] = "test-app2-id";
+const char kTestAppIdSkipUpdatecheck[] = "test-app-id-skip-updatecheck";
 
 // This is a helper struct to allow unit tests build an update response with the
 // values they care about.
@@ -145,19 +147,16 @@
                           : "") +
            "</packages>"
            "<actions><action event=\"postinstall\" MetadataSize=\"11" +
-           (multi_package ? ":22" : "") + "\" ChromeOSVersion=\"" + version +
-           "\" MoreInfo=\"" + more_info_url + "\" Prompt=\"" + prompt +
+           (multi_package ? ":22" : "") + "\" MoreInfo=\"" + more_info_url +
+           "\" Prompt=\"" + prompt +
            "\" "
-           "IsDelta=\"true\" "
            "IsDeltaPayload=\"true" +
            (multi_package ? ":false" : "") +
            "\" "
            "MaxDaysToScatter=\"" +
            max_days_to_scatter +
            "\" "
-           "sha256=\"not-used\" "
-           "needsadmin=\"" +
-           needsadmin + "\" " +
+           "sha256=\"not-used\" " +
            (deadline.empty() ? "" : ("deadline=\"" + deadline + "\" ")) +
            (disable_p2p_for_downloading ? "DisableP2PForDownloading=\"true\" "
                                         : "") +
@@ -182,6 +181,9 @@
            (multi_app_no_update
                 ? "<app><updatecheck status=\"noupdate\"/></app>"
                 : "") +
+           (multi_app_skip_updatecheck
+                ? "<app appid=\"" + app_id_skip_updatecheck + "\"></app>"
+                : "") +
            "</response>";
   }
 
@@ -192,6 +194,8 @@
 
   string app_id = kTestAppId;
   string app_id2 = kTestAppId2;
+  string app_id_skip_updatecheck = kTestAppIdSkipUpdatecheck;
+  string current_version = kCurrentVersion;
   string version = "1.2.3.4";
   string version2 = "2.3.4.5";
   string more_info_url = "http://more/info";
@@ -200,7 +204,6 @@
   string codebase2 = "http://code/base/2/";
   string filename = "file.signed";
   string hash = "4841534831323334";
-  string needsadmin = "false";
   uint64_t size = 123;
   string deadline = "";
   string max_days_to_scatter = "7";
@@ -227,6 +230,8 @@
   bool multi_app_self_update = false;
   // Whether to include an additional app with status="noupdate".
   bool multi_app_no_update = false;
+  // Whether to include an additional app with no updatecheck tag.
+  bool multi_app_skip_updatecheck = false;
   // Whether to include more than one package in an app.
   bool multi_package = false;
 
@@ -297,7 +302,7 @@
     request_params_.set_os_sp("service_pack");
     request_params_.set_os_board("x86-generic");
     request_params_.set_app_id(kTestAppId);
-    request_params_.set_app_version("0.1.0.0");
+    request_params_.set_app_version(kCurrentVersion);
     request_params_.set_app_lang("en-US");
     request_params_.set_current_channel("unittest");
     request_params_.set_target_channel("unittest");
@@ -310,6 +315,8 @@
     request_params_.set_target_version_prefix("");
     request_params_.set_rollback_allowed(false);
     request_params_.set_is_powerwash_allowed(false);
+    request_params_.set_is_install(false);
+    request_params_.set_dlc_module_ids({});
 
     fake_system_state_.set_request_params(&request_params_);
     fake_system_state_.set_prefs(&fake_prefs_);
@@ -1157,12 +1164,45 @@
   EXPECT_FALSE(response.update_exists);
 }
 
+// Verify that non-critical updates are skipped by reporting the
+// kNonCriticalUpdateInOOBE error code when attempted over cellular network -
+// i.e. when the update would need user permission. Note that reporting
+// kOmahaUpdateIgnoredOverCellular error in this case  might cause undesired UX
+// in OOBE (warning the user about an update that will be skipped).
+TEST_F(OmahaRequestActionTest, SkipNonCriticalUpdatesInOOBEOverCellular) {
+  OmahaResponse response;
+  fake_system_state_.fake_hardware()->UnsetIsOOBEComplete();
+
+  MockConnectionManager mock_cm;
+  fake_system_state_.set_connection_manager(&mock_cm);
+
+  EXPECT_CALL(mock_cm, GetConnectionProperties(_, _))
+      .WillRepeatedly(DoAll(SetArgPointee<0>(ConnectionType::kCellular),
+                            SetArgPointee<1>(ConnectionTethering::kUnknown),
+                            Return(true)));
+  EXPECT_CALL(mock_cm, IsAllowedConnectionTypesForUpdateSet())
+      .WillRepeatedly(Return(false));
+
+  ASSERT_FALSE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
+                               -1,
+                               false,  // ping_only
+                               ErrorCode::kNonCriticalUpdateInOOBE,
+                               metrics::CheckResult::kParsingError,
+                               metrics::CheckReaction::kUnset,
+                               metrics::DownloadErrorCode::kUnset,
+                               &response,
+                               nullptr));
+  EXPECT_FALSE(response.update_exists);
+}
+
 TEST_F(OmahaRequestActionTest, WallClockBasedWaitAloneCausesScattering) {
   OmahaResponse response;
   request_params_.set_wall_clock_based_wait_enabled(true);
   request_params_.set_update_check_count_wait_enabled(false);
   request_params_.set_waiting_period(TimeDelta::FromDays(2));
 
+  fake_system_state_.fake_clock()->SetWallclockTime(Time::Now());
+
   ASSERT_FALSE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
                                -1,
                                false,  // ping_only
@@ -1238,6 +1278,8 @@
   request_params_.set_min_update_checks_needed(0);
   request_params_.set_max_update_checks_allowed(0);
 
+  fake_system_state_.fake_clock()->SetWallclockTime(Time::Now());
+
   ASSERT_TRUE(TestUpdateCheck(
                       fake_update_response_.GetUpdateResponse(),
                       -1,
@@ -1263,6 +1305,8 @@
   request_params_.set_min_update_checks_needed(1);
   request_params_.set_max_update_checks_allowed(8);
 
+  fake_system_state_.fake_clock()->SetWallclockTime(Time::Now());
+
   ASSERT_FALSE(TestUpdateCheck(
                       fake_update_response_.GetUpdateResponse(),
                       -1,
@@ -1301,6 +1345,8 @@
   request_params_.set_min_update_checks_needed(1);
   request_params_.set_max_update_checks_allowed(8);
 
+  fake_system_state_.fake_clock()->SetWallclockTime(Time::Now());
+
   ASSERT_TRUE(fake_prefs_.SetInt64(kPrefsUpdateCheckCount, 5));
 
   ASSERT_FALSE(TestUpdateCheck(
@@ -1343,6 +1389,8 @@
   request_params_.set_waiting_period(TimeDelta::FromDays(6));
   request_params_.set_update_check_count_wait_enabled(false);
 
+  fake_system_state_.fake_clock()->SetWallclockTime(Time::Now());
+
   ASSERT_TRUE(fake_prefs_.SetInt64(kPrefsWallClockStagingWaitPeriod, 6));
   // This should not prevent scattering due to staging.
   fake_update_response_.max_days_to_scatter = "0";
@@ -1631,12 +1679,9 @@
       "<packages><package hash=\"not-used\" name=\"f\" "
       "size=\"587\" hash_sha256=\"lkq34j5345\"/></packages>"
       "<actions><action event=\"postinstall\" "
-      "ChromeOSVersion=\"10.2.3.4\" "
       "Prompt=\"false\" "
-      "IsDelta=\"true\" "
       "IsDeltaPayload=\"false\" "
       "sha256=\"not-used\" "
-      "needsadmin=\"true\" "
       "/></actions></manifest></updatecheck></app></response>";
   LOG(INFO) << "Input Response = " << input_response;
 
@@ -2502,7 +2547,7 @@
                                metrics::DownloadErrorCode::kUnset,
                                nullptr,  // response
                                &post_data));
-  // convert post_data to string
+  // Convert post_data to string.
   string post_str(post_data.begin(), post_data.end());
   EXPECT_NE(string::npos, post_str.find(
       "appid=\"{11111111-1111-1111-1111-111111111111}\" "
@@ -3007,4 +3052,108 @@
   EXPECT_EQ(4, response.rollback_key_version.kernel);
 }
 
+TEST_F(OmahaRequestActionTest,
+       TestUpdateFirstSeenAtPrefPersistedIfUpdateExists) {
+  FakeClock fake_clock;
+  Time now = Time::Now();
+  fake_clock.SetWallclockTime(now);
+  fake_system_state_.set_clock(&fake_clock);
+
+  OmahaResponse response;
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kUpdateAvailable,
+                              metrics::CheckReaction::kUpdating,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
+  EXPECT_TRUE(response.update_exists);
+  EXPECT_TRUE(fake_prefs_.Exists(kPrefsUpdateFirstSeenAt));
+
+  int64_t stored_first_seen_at_time;
+  EXPECT_TRUE(fake_prefs_.GetInt64(kPrefsUpdateFirstSeenAt,
+                                   &stored_first_seen_at_time));
+  EXPECT_EQ(now.ToInternalValue(), stored_first_seen_at_time);
+}
+
+TEST_F(OmahaRequestActionTest,
+       TestUpdateFirstSeenAtPrefNotPersistedIfUpdateFails) {
+  FakeClock fake_clock;
+  Time now = Time::Now();
+  fake_clock.SetWallclockTime(now);
+  fake_system_state_.set_clock(&fake_clock);
+
+  OmahaResponse response;
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetNoUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kNoUpdateAvailable,
+                              metrics::CheckReaction::kUnset,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
+  EXPECT_FALSE(response.update_exists);
+  EXPECT_FALSE(fake_prefs_.Exists(kPrefsUpdateFirstSeenAt));
+}
+
+TEST_F(OmahaRequestActionTest, InstallTest) {
+  OmahaResponse response;
+  request_params_.set_is_install(true);
+  request_params_.set_dlc_module_ids({"dlc_no_0", "dlc_no_1"});
+  brillo::Blob post_data;
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              true,   // is_consumer_device
+                              0,      // rollback_allowed_milestones
+                              false,  // is_policy_loaded
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kUpdateAvailable,
+                              metrics::CheckReaction::kUpdating,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              &post_data));
+  // Convert post_data to string.
+  string post_str(post_data.begin(), post_data.end());
+  for (const auto& dlc_module_id : request_params_.dlc_module_ids()) {
+    EXPECT_NE(string::npos,
+              post_str.find("appid=\"" + fake_update_response_.app_id + "_" +
+                            dlc_module_id + "\""));
+  }
+  EXPECT_NE(string::npos,
+            post_str.find("appid=\"" + fake_update_response_.app_id + "\""));
+
+  // Count number of updatecheck tag in response.
+  int updatecheck_count = 0;
+  size_t pos = 0;
+  while ((pos = post_str.find("<updatecheck", pos)) != string::npos) {
+    updatecheck_count++;
+    pos++;
+  }
+  EXPECT_EQ(request_params_.dlc_module_ids().size(), updatecheck_count);
+}
+
+TEST_F(OmahaRequestActionTest, InstallMissingPlatformVersionTest) {
+  fake_update_response_.multi_app_skip_updatecheck = true;
+  fake_update_response_.multi_app_no_update = false;
+  request_params_.set_is_install(true);
+  request_params_.set_dlc_module_ids({"dlc_no_0", "dlc_no_1"});
+  request_params_.set_app_id(fake_update_response_.app_id_skip_updatecheck);
+  OmahaResponse response;
+  ASSERT_TRUE(TestUpdateCheck(fake_update_response_.GetUpdateResponse(),
+                              -1,
+                              false,  // ping_only
+                              ErrorCode::kSuccess,
+                              metrics::CheckResult::kUpdateAvailable,
+                              metrics::CheckReaction::kUpdating,
+                              metrics::DownloadErrorCode::kUnset,
+                              &response,
+                              nullptr));
+  EXPECT_TRUE(response.update_exists);
+  EXPECT_EQ(fake_update_response_.current_version, response.version);
+}
+
 }  // namespace chromeos_update_engine
diff --git a/omaha_request_params.cc b/omaha_request_params.cc
index 186ea61..c458424 100644
--- a/omaha_request_params.cc
+++ b/omaha_request_params.cc
@@ -122,6 +122,10 @@
 
   // Set the interactive flag accordingly.
   interactive_ = in_interactive;
+
+  dlc_module_ids_.clear();
+  // Set false so it will do update by default.
+  is_install_ = false;
   return true;
 }
 
diff --git a/omaha_request_params.h b/omaha_request_params.h
index c8e26b5..1bfa471 100644
--- a/omaha_request_params.h
+++ b/omaha_request_params.h
@@ -20,6 +20,7 @@
 #include <stdint.h>
 
 #include <string>
+#include <vector>
 
 #include <base/macros.h>
 #include <base/time/time.h>
@@ -53,7 +54,8 @@
         wall_clock_based_wait_enabled_(false),
         update_check_count_wait_enabled_(false),
         min_update_checks_needed_(kDefaultMinUpdateChecks),
-        max_update_checks_allowed_(kDefaultMaxUpdateChecks) {}
+        max_update_checks_allowed_(kDefaultMaxUpdateChecks),
+        is_install_(false) {}
 
   virtual ~OmahaRequestParams();
 
@@ -163,6 +165,15 @@
   inline int64_t max_update_checks_allowed() const {
     return max_update_checks_allowed_;
   }
+  inline void set_dlc_module_ids(
+      const std::vector<std::string>& dlc_module_ids) {
+    dlc_module_ids_ = dlc_module_ids;
+  }
+  inline std::vector<std::string> dlc_module_ids() const {
+    return dlc_module_ids_;
+  }
+  inline void set_is_install(bool is_install) { is_install_ = is_install; }
+  inline bool is_install() const { return is_install_; }
 
   // Returns the app id corresponding to the current value of the
   // download channel.
@@ -328,6 +339,14 @@
   // When reading files, prepend root_ to the paths. Useful for testing.
   std::string root_;
 
+  // A list of DLC module IDs to install.
+  std::vector<std::string> dlc_module_ids_;
+
+  // This variable defines whether the payload is being installed in the current
+  // partition. At the moment, this is used for installing DLC modules on the
+  // current active partition instead of the inactive partition.
+  bool is_install_;
+
   DISALLOW_COPY_AND_ASSIGN(OmahaRequestParams);
 };
 
diff --git a/omaha_response_handler_action.cc b/omaha_response_handler_action.cc
index c1fe854..ab41b84 100644
--- a/omaha_response_handler_action.cc
+++ b/omaha_response_handler_action.cc
@@ -121,8 +121,13 @@
         << "Unable to save the update check response hash.";
   }
 
-  install_plan_.source_slot = system_state_->boot_control()->GetCurrentSlot();
-  install_plan_.target_slot = install_plan_.source_slot == 0 ? 1 : 0;
+  if (params->is_install()) {
+    install_plan_.target_slot = system_state_->boot_control()->GetCurrentSlot();
+    install_plan_.source_slot = BootControlInterface::kInvalidSlot;
+  } else {
+    install_plan_.source_slot = system_state_->boot_control()->GetCurrentSlot();
+    install_plan_.target_slot = install_plan_.source_slot == 0 ? 1 : 0;
+  }
 
   // The Omaha response doesn't include the channel name for this image, so we
   // use the download_channel we used during the request to tag the target slot.
diff --git a/omaha_response_handler_action_unittest.cc b/omaha_response_handler_action_unittest.cc
index e4d05f4..b128b27 100644
--- a/omaha_response_handler_action_unittest.cc
+++ b/omaha_response_handler_action_unittest.cc
@@ -140,7 +140,10 @@
                 SetString(kPrefsUpdateCheckResponseHash, expected_hash))
         .WillOnce(Return(true));
 
-    int slot = 1 - fake_system_state_.fake_boot_control()->GetCurrentSlot();
+    int slot =
+        fake_system_state_.request_params()->is_install()
+            ? fake_system_state_.fake_boot_control()->GetCurrentSlot()
+            : 1 - fake_system_state_.fake_boot_control()->GetCurrentSlot();
     string key = kPrefsChannelOnSlotPrefix + std::to_string(slot);
     EXPECT_CALL(*(fake_system_state_.mock_prefs()), SetString(key, testing::_))
         .WillOnce(Return(true));
@@ -284,6 +287,25 @@
   EXPECT_TRUE(install_plan.partitions.empty());
 }
 
+TEST_F(OmahaResponseHandlerActionTest, InstallTest) {
+  OmahaResponse in;
+  in.update_exists = true;
+  in.version = "a.b.c.d";
+  in.packages.push_back(
+      {.payload_urls = {kLongName}, .size = 1, .hash = kPayloadHashHex});
+  in.packages.push_back(
+      {.payload_urls = {kLongName}, .size = 2, .hash = kPayloadHashHex});
+  in.more_info_url = "http://more/info";
+
+  OmahaRequestParams params(&fake_system_state_);
+  params.set_is_install(true);
+
+  fake_system_state_.set_request_params(&params);
+  InstallPlan install_plan;
+  EXPECT_TRUE(DoTest(in, "", &install_plan));
+  EXPECT_EQ(install_plan.source_slot, UINT_MAX);
+}
+
 TEST_F(OmahaResponseHandlerActionTest, MultiPackageTest) {
   OmahaResponse in;
   in.update_exists = true;
diff --git a/payload_consumer/delta_performer.cc b/payload_consumer/delta_performer.cc
index 1bfdf9c..7dcb5f7 100644
--- a/payload_consumer/delta_performer.cc
+++ b/payload_consumer/delta_performer.cc
@@ -71,6 +71,7 @@
 const unsigned DeltaPerformer::kProgressLogTimeoutSeconds = 30;
 const unsigned DeltaPerformer::kProgressDownloadWeight = 50;
 const unsigned DeltaPerformer::kProgressOperationsWeight = 50;
+const uint64_t DeltaPerformer::kCheckpointFrequencySeconds = 1;
 
 namespace {
 const int kUpdateStateOperationInvalid = -1;
@@ -1901,6 +1902,13 @@
 }
 
 bool DeltaPerformer::CheckpointUpdateProgress() {
+  base::Time curr_time = base::Time::Now();
+  if (curr_time > update_checkpoint_time_) {
+    update_checkpoint_time_ = curr_time + update_checkpoint_wait_;
+  } else {
+    return false;
+  }
+
   Terminator::set_exit_blocked(true);
   if (last_updated_buffer_offset_ != buffer_offset_) {
     // Resets the progress in case we die in the middle of the state update.
diff --git a/payload_consumer/delta_performer.h b/payload_consumer/delta_performer.h
index cddfef4..8597a37 100644
--- a/payload_consumer/delta_performer.h
+++ b/payload_consumer/delta_performer.h
@@ -60,6 +60,7 @@
   // operations. They must add up to one hundred (100).
   static const unsigned kProgressDownloadWeight;
   static const unsigned kProgressOperationsWeight;
+  static const uint64_t kCheckpointFrequencySeconds;
 
   DeltaPerformer(PrefsInterface* prefs,
                  BootControlInterface* boot_control,
@@ -400,6 +401,12 @@
       base::TimeDelta::FromSeconds(kProgressLogTimeoutSeconds)};
   base::Time forced_progress_log_time_;
 
+  // The frequency that we should write an update checkpoint (constant), and
+  // the point in time at which the next checkpoint should be written.
+  const base::TimeDelta update_checkpoint_wait_{
+      base::TimeDelta::FromSeconds(kCheckpointFrequencySeconds)};
+  base::Time update_checkpoint_time_;
+
   DISALLOW_COPY_AND_ASSIGN(DeltaPerformer);
 };
 
diff --git a/payload_generator/generate_delta_main.cc b/payload_generator/generate_delta_main.cc
index a7ec6da..05486c5 100644
--- a/payload_generator/generate_delta_main.cc
+++ b/payload_generator/generate_delta_main.cc
@@ -353,7 +353,7 @@
   DEFINE_uint64(rootfs_partition_size,
                chromeos_update_engine::kRootFSPartitionSize,
                "RootFS partition size for the image once installed");
-  DEFINE_uint64(major_version, 1,
+  DEFINE_uint64(major_version, 2,
                "The major version of the payload being generated.");
   DEFINE_int32(minor_version, -1,
                "The minor version of the payload being generated "
diff --git a/payload_state.cc b/payload_state.cc
index 36f120a..ab7912e 100644
--- a/payload_state.cc
+++ b/payload_state.cc
@@ -55,6 +55,9 @@
 // We want to randomize retry attempts after the backoff by +/- 6 hours.
 static const uint32_t kMaxBackoffFuzzMinutes = 12 * 60;
 
+// Limit persisting current update duration uptime to once per second
+static const uint64_t kUptimeResolution = 1;
+
 PayloadState::PayloadState()
     : prefs_(nullptr),
       using_p2p_for_downloading_(false),
@@ -1153,9 +1156,12 @@
 void PayloadState::CalculateUpdateDurationUptime() {
   Time now = system_state_->clock()->GetMonotonicTime();
   TimeDelta uptime_since_last_update = now - update_duration_uptime_timestamp_;
-  TimeDelta new_uptime = update_duration_uptime_ + uptime_since_last_update;
-  // We're frequently called so avoid logging this write
-  SetUpdateDurationUptimeExtended(new_uptime, now, false);
+
+  if (uptime_since_last_update > TimeDelta::FromSeconds(kUptimeResolution)) {
+    TimeDelta new_uptime = update_duration_uptime_ + uptime_since_last_update;
+    // We're frequently called so avoid logging this write
+    SetUpdateDurationUptimeExtended(new_uptime, now, false);
+  }
 }
 
 string PayloadState::GetPrefsKey(const string& prefix, DownloadSource source) {
diff --git a/real_system_state.cc b/real_system_state.cc
index 9dab3a1..f576c6c 100644
--- a/real_system_state.cc
+++ b/real_system_state.cc
@@ -32,6 +32,7 @@
 #include "update_engine/common/boot_control.h"
 #include "update_engine/common/boot_control_stub.h"
 #include "update_engine/common/constants.h"
+#include "update_engine/common/dlcservice.h"
 #include "update_engine/common/hardware.h"
 #include "update_engine/common/utils.h"
 #include "update_engine/metrics_reporter_omaha.h"
@@ -88,6 +89,12 @@
     return false;
   }
 
+  dlcservice_ = CreateDlcService();
+  if (!dlcservice_) {
+    LOG(ERROR) << "Error initializing the DlcServiceInterface.";
+    return false;
+  }
+
   // Initialize standard and powerwash-safe prefs.
   base::FilePath non_volatile_path;
   // TODO(deymo): Fall back to in-memory prefs if there's no physical directory
diff --git a/real_system_state.h b/real_system_state.h
index 5239160..4712008 100644
--- a/real_system_state.h
+++ b/real_system_state.h
@@ -31,6 +31,7 @@
 #include "update_engine/certificate_checker.h"
 #include "update_engine/common/boot_control_interface.h"
 #include "update_engine/common/clock.h"
+#include "update_engine/common/dlcservice_interface.h"
 #include "update_engine/common/hardware_interface.h"
 #include "update_engine/common/prefs.h"
 #include "update_engine/connection_manager_interface.h"
@@ -126,6 +127,10 @@
 
   inline bool system_rebooted() override { return system_rebooted_; }
 
+  inline DlcServiceInterface* dlcservice() override {
+    return dlcservice_.get();
+  }
+
  private:
   // Real DBus proxies using the DBus connection.
 #if USE_CHROME_KIOSK_APP
@@ -136,6 +141,9 @@
   // Interface for the power manager.
   std::unique_ptr<PowerManagerInterface> power_manager_;
 
+  // Interface for dlcservice.
+  std::unique_ptr<DlcServiceInterface> dlcservice_;
+
   // Interface for the clock.
   std::unique_ptr<BootControlInterface> boot_control_;
 
diff --git a/sample_omaha_v3_response.xml b/sample_omaha_v3_response.xml
index abba523..1aec1f2 100644
--- a/sample_omaha_v3_response.xml
+++ b/sample_omaha_v3_response.xml
@@ -1,18 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
 <response protocol="3.0" server="prod">
-  <daystart elapsed_seconds="56652"/>
-  <app appid="{90f229ce-83e2-4faf-8479-e368a34938b1}" status="ok">
+  <daystart elapsed_days="4086" elapsed_seconds="62499"/>
+  <app appid="{C166AF52-7EE9-4F08-AAA7-B4B895A9F336}" cohort="1:3:" cohortname="caroline_beta" status="ok">
+    <ping status="ok"/>
     <updatecheck status="ok">
       <urls>
-        <url codebase="https://storage.googleapis.com/chromeos-releases-public/canary-channel/canary-channel/3095.0.0/"/>
+        <url codebase="http://dl.google.com/chromeos/caroline/10323.52.0/beta-channel/"/>
+        <url codebase="https://dl.google.com/chromeos/caroline/10323.52.0/beta-channel/"/>
       </urls>
-      <manifest version="3095.0.0">
-        <packages>
-          <package hash="HVOmp67vBjPdvpWmOC2Uw4UDwsc=" name="chromeos_3095.0.0_x86-zgb_canary-channel_full_mp-v2.bin-df37843370ddf1e3819a2afeaa934faa.signed" required="true" size="400752559"/>
-        </packages>
+      <manifest version="10323.52.0">
         <actions>
-          <action event="update" run="chromeos_3095.0.0_x86-zgb_canary-channel_full_mp-v2.bin-df37843370ddf1e3819a2afeaa934faa.signed"/>
-          <action ChromeOSVersion="3095.0.0" ChromeVersion="24.0.1307.0" IsDelta="true" IsDeltaPayload="false" MaxDaysToScatter="14" MetadataSignatureRsa="xXrO/LahHlKk3YmqEf1qE0PN587Sc2IJV+FN7J7x1h49waNQIy/QwYO4LaOySgETe5JZXtkAEzzqakfJwxQ2pVfzj1GkExwjd5LTn1He2GvA73B8fKbS4bfP7dbUFwD5039xCwf1U2gezFViOiOPiVURx/pEsdhv+Cqx/3HbjIuj5au2dooSyDxLC5AnODzAKyYfAcjMuiLON+9SqmctJW+VjzdY9SbJAnkH2qqVjFyBKAXsYT+hOTIJ3MJpg8OSVxMMtGB99PxbOJ52F37d2Y5Fws/AUkNnNEsan/WRJA1kuWoS6rpeR8JQYuVhLiK2u/KpOcvMVRw3Q2VUxtcAGw==" MetadataSize="58315" event="postinstall" sha256="DIAVxoI+8NpsudUawOA5U92VHlaxQBS3ejN4EPM6T2A="/>
+          <action event="update" run="chromeos_10323.46.0-10323.52.0_caroline_beta-channel_delta_mp.bin-f5c4e5e263c4c119d7d22e0f18a586e5.signed"/>
+          <action ChromeOSVersion="10323.52.0" ChromeVersion="65.0.3325.148" IsDelta="true" IsDeltaPayload="true" MaxDaysToScatter="14" MetadataSignatureRsa="tkrOiIQn2GMQzjLckjiiOyuyV+RqupNW50t6JlFWOhAzWM8dm1qrJVYTYlULxTVlx4BHijbNuX7+OYk6zhRuxuceY7sUwrCM2yxERZ/sDLA5wF0u/8KLP7qrDKL2OIk9JJhF0EdLPylUAEt6vWW4pbYRFhK0girgWIPSdqdjkfHNTKWEUtcQ3iAAB8AvLNOyGP/en0makFvSVXZ8Mq95UrSwWMYFdVmWdVkyRtLYSwLaz5J45y3DQuk3YjeaHhRlH/AQ3OJXX6rjTCwgyiddAccOalwFVwrczq6AUs5S+/vWAMqi+7YfCPgjRdPPIhRJVKcIiAPb8RNXlP+rigGwew==" MetadataSize="414487" event="postinstall" sha256="kONiEAWQV7UyBjOoFBcKDz0OkUx0yRuIGzse4O6rmDs="/>
         </actions>
+        <packages>
+          <package fp="1.90e36210059057b5320633a814170a0f3d0e914c74c91b881b3b1ee0eeab983b" hash="gD1W+dPZiNEhz3f3odCtfL81Yi8=" hash_sha256="90e36210059057b5320633a814170a0f3d0e914c74c91b881b3b1ee0eeab983b" name="chromeos_10323.46.0-10323.52.0_caroline_beta-channel_delta_mp.bin-f5c4e5e263c4c119d7d22e0f18a586e5.signed" required="true" size="29981022"/>
+        </packages>
       </manifest>
     </updatecheck>
   </app>
diff --git a/scripts/brillo_update_payload b/scripts/brillo_update_payload
index 5c6e6d7..c88709c 100755
--- a/scripts/brillo_update_payload
+++ b/scripts/brillo_update_payload
@@ -90,7 +90,7 @@
 load_shflags() {
   local my_dir="$(dirname "$(readlink -f "$0")")"
   local path
-  for path in /usr/share/misc {/usr/lib/crosutils,"${my_dir}"}/lib/shflags; do
+  for path in /usr/share/misc "${my_dir}"/lib/shflags; do
     if [[ -r "${path}/shflags" ]]; then
       . "${path}/shflags" || die "Could not load ${path}/shflags."
       return
@@ -371,12 +371,11 @@
 
   cros_generate_update_payload --extract \
     --image "${image}" \
-    --kern_path "${kernel}" --root_path "${root}" \
-    --work_dir "${FLAGS_work_dir}" --outside_chroot
+    --kern_path "${kernel}" --root_path "${root}"
 
-  # Chrome OS uses major_version 1 payloads for all versions, even if the
-  # updater supports a newer major version.
-  FORCE_MAJOR_VERSION="1"
+  # Chrome OS now uses major_version 2 payloads for all boards.
+  # See crbug.com/794404 for more information.
+  FORCE_MAJOR_VERSION="2"
 
   eval ${partitions_array}[kernel]=\""${kernel}"\"
   eval ${partitions_array}[root]=\""${root}"\"
diff --git a/scripts/update_payload/checker.py b/scripts/update_payload/checker.py
index 746d4be..15f11ae 100644
--- a/scripts/update_payload/checker.py
+++ b/scripts/update_payload/checker.py
@@ -627,7 +627,7 @@
     self._CheckPresentIff(self.sigs_offset, self.sigs_size,
                           'signatures_offset', 'signatures_size', 'manifest')
 
-    if self.major_version == 1:
+    if self.major_version == common.CHROMEOS_MAJOR_PAYLOAD_VERSION:
       for real_name, proto_name in common.CROS_PARTITIONS:
         self.old_part_info[real_name] = self._CheckOptionalSubMsg(
             manifest, 'old_%s_info' % proto_name, report)
@@ -1069,8 +1069,9 @@
     # Type-specific checks.
     if op.type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ):
       self._CheckReplaceOperation(op, data_length, total_dst_blocks, op_name)
-    elif op.type == common.OpType.REPLACE_XZ and (self.minor_version >= 3 or
-                                                  self.major_version >= 2):
+    elif (op.type == common.OpType.REPLACE_XZ and
+          (self.minor_version >= 3 or
+           self.major_version >= common.BRILLO_MAJOR_PAYLOAD_VERSION)):
       self._CheckReplaceOperation(op, data_length, total_dst_blocks, op_name)
     elif op.type == common.OpType.MOVE and self.minor_version == 1:
       self._CheckMoveOperation(op, data_offset, total_src_blocks,
@@ -1251,16 +1252,19 @@
 
     last_ops_section = (self.payload.manifest.kernel_install_operations or
                         self.payload.manifest.install_operations)
-    fake_sig_op = last_ops_section[-1]
-    # Check: signatures_{offset,size} must match the last (fake) operation.
-    if not (fake_sig_op.type == common.OpType.REPLACE and
-            self.sigs_offset == fake_sig_op.data_offset and
-            self.sigs_size == fake_sig_op.data_length):
-      raise error.PayloadError(
-          'Signatures_{offset,size} (%d+%d) does not match last operation '
-          '(%d+%d).' %
-          (self.sigs_offset, self.sigs_size, fake_sig_op.data_offset,
-           fake_sig_op.data_length))
+
+    # Only major version 1 has the fake signature OP at the end.
+    if self.major_version == common.CHROMEOS_MAJOR_PAYLOAD_VERSION:
+      fake_sig_op = last_ops_section[-1]
+      # Check: signatures_{offset,size} must match the last (fake) operation.
+      if not (fake_sig_op.type == common.OpType.REPLACE and
+              self.sigs_offset == fake_sig_op.data_offset and
+              self.sigs_size == fake_sig_op.data_length):
+        raise error.PayloadError('Signatures_{offset,size} (%d+%d) does not'
+                                 ' match last operation (%d+%d).' %
+                                 (self.sigs_offset, self.sigs_size,
+                                  fake_sig_op.data_offset,
+                                  fake_sig_op.data_length))
 
     # Compute the checksum of all data up to signature blob.
     # TODO(garnold) we're re-reading the whole data section into a string
@@ -1314,9 +1318,9 @@
 
     try:
       # Check metadata_size (if provided).
-      if metadata_size and self.payload.data_offset != metadata_size:
+      if metadata_size and self.payload.metadata_size != metadata_size:
         raise error.PayloadError('Invalid payload metadata size in payload(%d) '
-                                 'vs given(%d)' % (self.payload.data_offset,
+                                 'vs given(%d)' % (self.payload.metadata_size,
                                                    metadata_size))
 
       # Check metadata signature (if provided).
@@ -1343,7 +1347,7 @@
 
       # Part 3: Examine partition operations.
       install_operations = []
-      if self.major_version == 1:
+      if self.major_version == common.CHROMEOS_MAJOR_PAYLOAD_VERSION:
         # partitions field should not ever exist in major version 1 payloads
         self._CheckRepeatedElemNotPresent(manifest, 'partitions', 'manifest')
 
@@ -1386,10 +1390,17 @@
             operations, report, '%s_install_operations' % part,
             self.old_fs_sizes[part], self.new_fs_sizes[part],
             old_fs_usable_size, new_fs_usable_size, total_blob_size,
-            self.major_version == 1 and part == common.KERNEL)
+            (self.major_version == common.CHROMEOS_MAJOR_PAYLOAD_VERSION
+             and part == common.KERNEL))
 
       # Check: Operations data reach the end of the payload file.
       used_payload_size = self.payload.data_offset + total_blob_size
+      # Major versions 2 and higher have a signature at the end, so it should be
+      # considered in the total size of the image.
+      if (self.major_version >= common.BRILLO_MAJOR_PAYLOAD_VERSION and
+          self.sigs_size):
+        used_payload_size += self.sigs_size
+
       if used_payload_size != payload_file_size:
         raise error.PayloadError(
             'Used payload size (%d) different from actual file size (%d).' %
diff --git a/scripts/update_payload/checker_unittest.py b/scripts/update_payload/checker_unittest.py
index 98bf612..7e52233 100755
--- a/scripts/update_payload/checker_unittest.py
+++ b/scripts/update_payload/checker_unittest.py
@@ -892,7 +892,7 @@
         total_src_blocks = 16
 
     # TODO(tbrindus): add major version 2 tests.
-    payload_checker.major_version = 1
+    payload_checker.major_version = common.CHROMEOS_MAJOR_PAYLOAD_VERSION
     if op_type in (common.OpType.REPLACE, common.OpType.REPLACE_BZ):
       payload_checker.minor_version = 0
     elif op_type in (common.OpType.MOVE, common.OpType.BSDIFF):
diff --git a/system_state.h b/system_state.h
index 1b0ad08..f46cbcf 100644
--- a/system_state.h
+++ b/system_state.h
@@ -37,6 +37,7 @@
 class BootControlInterface;
 class ClockInterface;
 class ConnectionManagerInterface;
+class DlcServiceInterface;
 class HardwareInterface;
 class MetricsReporterInterface;
 class OmahaRequestParams;
@@ -109,6 +110,9 @@
   // restarted. Important for tracking whether you are running instance of the
   // update engine on first boot or due to a crash/restart.
   virtual bool system_rebooted() = 0;
+
+  // Returns a pointer to the DlcServiceInterface singleton.
+  virtual DlcServiceInterface* dlcservice() = 0;
 };
 
 }  // namespace chromeos_update_engine
diff --git a/update_attempter.cc b/update_attempter.cc
index 63d8a61..5efd257 100644
--- a/update_attempter.cc
+++ b/update_attempter.cc
@@ -30,6 +30,7 @@
 #include <base/rand_util.h>
 #include <base/strings/string_util.h>
 #include <base/strings/stringprintf.h>
+#include <base/time/time.h>
 #include <brillo/data_encoding.h>
 #include <brillo/errors/error_codes.h>
 #include <brillo/message_loops/message_loop.h>
@@ -41,6 +42,7 @@
 #include "update_engine/common/boot_control_interface.h"
 #include "update_engine/common/clock_interface.h"
 #include "update_engine/common/constants.h"
+#include "update_engine/common/dlcservice_interface.h"
 #include "update_engine/common/hardware_interface.h"
 #include "update_engine/common/platform_constants.h"
 #include "update_engine/common/prefs_interface.h"
@@ -123,7 +125,8 @@
                                  CertificateChecker* cert_checker)
     : processor_(new ActionProcessor()),
       system_state_(system_state),
-      cert_checker_(cert_checker) {}
+      cert_checker_(cert_checker),
+      is_install_(false) {}
 
 UpdateAttempter::~UpdateAttempter() {
   // CertificateChecker might not be initialized in unittests.
@@ -152,9 +155,9 @@
     status_ = UpdateStatus::IDLE;
 }
 
-void UpdateAttempter::ScheduleUpdates() {
+bool UpdateAttempter::ScheduleUpdates() {
   if (IsUpdateRunningOrScheduled())
-    return;
+    return false;
 
   chromeos_update_manager::UpdateManager* const update_manager =
       system_state_->update_manager();
@@ -165,6 +168,7 @@
   // starvation due to a transient bug.
   update_manager->AsyncPolicyRequest(callback, &Policy::UpdateCheckAllowed);
   waiting_for_scheduled_check_ = true;
+  return true;
 }
 
 void UpdateAttempter::CertificateChecked(ServerToCheck server_to_check,
@@ -411,6 +415,9 @@
     // target channel.
     omaha_request_params_->UpdateDownloadChannel();
   }
+  // Set the DLC module ID list.
+  omaha_request_params_->set_dlc_module_ids(dlc_module_ids_);
+  omaha_request_params_->set_is_install(is_install_);
 
   LOG(INFO) << "target_version_prefix = "
             << omaha_request_params_->target_version_prefix()
@@ -692,6 +699,7 @@
 }
 
 bool UpdateAttempter::Rollback(bool powerwash) {
+  is_install_ = false;
   if (!CanRollback()) {
     return false;
   }
@@ -786,6 +794,8 @@
 bool UpdateAttempter::CheckForUpdate(const string& app_version,
                                      const string& omaha_url,
                                      UpdateAttemptFlags flags) {
+  dlc_module_ids_.clear();
+  is_install_ = false;
   bool interactive = !(flags & UpdateAttemptFlags::kFlagNonInteractive);
 
   if (interactive && status_ != UpdateStatus::IDLE) {
@@ -827,6 +837,9 @@
   }
 
   if (forced_update_pending_callback_.get()) {
+    if (!system_state_->dlcservice()->GetInstalled(&dlc_module_ids_)) {
+      dlc_module_ids_.clear();
+    }
     // Make sure that a scheduling request is made prior to calling the forced
     // update pending callback.
     ScheduleUpdates();
@@ -836,6 +849,37 @@
   return true;
 }
 
+bool UpdateAttempter::CheckForInstall(const vector<string>& dlc_module_ids,
+                                      const string& omaha_url) {
+  dlc_module_ids_ = dlc_module_ids;
+  is_install_ = true;
+  forced_omaha_url_.clear();
+
+  // Certain conditions must be met to allow setting custom version and update
+  // server URLs. However, kScheduledAUTestURLRequest and kAUTestURLRequest are
+  // always allowed regardless of device state.
+  if (IsAnyUpdateSourceAllowed()) {
+    forced_omaha_url_ = omaha_url;
+  }
+  if (omaha_url == kScheduledAUTestURLRequest) {
+    forced_omaha_url_ = constants::kOmahaDefaultAUTestURL;
+  } else if (omaha_url == kAUTestURLRequest) {
+    forced_omaha_url_ = constants::kOmahaDefaultAUTestURL;
+  }
+
+  if (!ScheduleUpdates()) {
+    if (forced_update_pending_callback_.get()) {
+      // Make sure that a scheduling request is made prior to calling the forced
+      // update pending callback.
+      ScheduleUpdates();
+      forced_update_pending_callback_->Run(true, true);
+      return true;
+    }
+    return false;
+  }
+  return true;
+}
+
 bool UpdateAttempter::RebootIfNeeded() {
 #ifdef __ANDROID__
   if (status_ != UpdateStatus::UPDATED_NEED_REBOOT) {
@@ -975,7 +1019,12 @@
   attempt_error_code_ = utils::GetBaseErrorCode(code);
 
   if (code == ErrorCode::kSuccess) {
-    WriteUpdateCompletedMarker();
+    // For install operation, we do not mark update complete since we do not
+    // need reboot.
+    if (!is_install_)
+      WriteUpdateCompletedMarker();
+    ReportTimeToUpdateAppliedMetric();
+
     prefs_->SetInt64(kPrefsDeltaUpdateFailures, 0);
     prefs_->SetString(kPrefsPreviousVersion,
                       omaha_request_params_->app_version());
@@ -997,6 +1046,13 @@
     system_state_->payload_state()->SetStagingWaitPeriod(TimeDelta());
     prefs_->Delete(kPrefsUpdateFirstSeenAt);
 
+    if (is_install_) {
+      LOG(INFO) << "DLC successfully installed, no reboot needed.";
+      SetStatusAndNotify(UpdateStatus::IDLE);
+      ScheduleUpdates();
+      return;
+    }
+
     SetStatusAndNotify(UpdateStatus::UPDATED_NEED_REBOOT);
     ScheduleUpdates();
     LOG(INFO) << "Update successfully applied, waiting to reboot.";
@@ -1604,4 +1660,26 @@
   return false;
 }
 
+void UpdateAttempter::ReportTimeToUpdateAppliedMetric() {
+  const policy::DevicePolicy* device_policy = system_state_->device_policy();
+  if (device_policy && device_policy->IsEnterpriseEnrolled()) {
+    vector<policy::DevicePolicy::WeeklyTimeInterval> parsed_intervals;
+    bool has_time_restrictions =
+        device_policy->GetDisallowedTimeIntervals(&parsed_intervals);
+
+    int64_t update_first_seen_at_int;
+    if (system_state_->prefs()->Exists(kPrefsUpdateFirstSeenAt)) {
+      if (system_state_->prefs()->GetInt64(kPrefsUpdateFirstSeenAt,
+                                           &update_first_seen_at_int)) {
+        TimeDelta update_delay =
+            system_state_->clock()->GetWallclockTime() -
+            Time::FromInternalValue(update_first_seen_at_int);
+        system_state_->metrics_reporter()
+            ->ReportEnterpriseUpdateSeenToDownloadDays(has_time_restrictions,
+                                                       update_delay.InDays());
+      }
+    }
+  }
+}
+
 }  // namespace chromeos_update_engine
diff --git a/update_attempter.h b/update_attempter.h
index d0beff6..af62ba6 100644
--- a/update_attempter.h
+++ b/update_attempter.h
@@ -69,7 +69,8 @@
   void Init();
 
   // Initiates scheduling of update checks.
-  virtual void ScheduleUpdates();
+  // Returns true if update check is scheduled.
+  virtual bool ScheduleUpdates();
 
   // Checks for update and, if a newer version is available, attempts to update
   // the system. Non-empty |in_app_version| or |in_update_url| prevents
@@ -135,6 +136,10 @@
                               const std::string& omaha_url,
                               UpdateAttemptFlags flags);
 
+  // This is the version of CheckForUpdate called by AttemptInstall API.
+  virtual bool CheckForInstall(const std::vector<std::string>& dlc_module_ids,
+                               const std::string& omaha_url);
+
   // This is the internal entry point for going through a rollback. This will
   // attempt to run the postinstall on the non-active partition and set it as
   // the partition to boot from. If |powerwash| is True, perform a powerwash
@@ -241,14 +246,17 @@
   FRIEND_TEST(UpdateAttempterTest, BootTimeInUpdateMarkerFile);
   FRIEND_TEST(UpdateAttempterTest, BroadcastCompleteDownloadTest);
   FRIEND_TEST(UpdateAttempterTest, ChangeToDownloadingOnReceivedBytesTest);
+  FRIEND_TEST(UpdateAttempterTest, CheckForUpdateAUDlcTest);
   FRIEND_TEST(UpdateAttempterTest, CreatePendingErrorEventTest);
   FRIEND_TEST(UpdateAttempterTest, CreatePendingErrorEventResumedTest);
   FRIEND_TEST(UpdateAttempterTest, DisableDeltaUpdateIfNeededTest);
   FRIEND_TEST(UpdateAttempterTest, DownloadProgressAccumulationTest);
+  FRIEND_TEST(UpdateAttempterTest, InstallSetsStatusIdle);
   FRIEND_TEST(UpdateAttempterTest, MarkDeltaUpdateFailureTest);
   FRIEND_TEST(UpdateAttempterTest, PingOmahaTest);
   FRIEND_TEST(UpdateAttempterTest, ReportDailyMetrics);
   FRIEND_TEST(UpdateAttempterTest, RollbackNotAllowed);
+  FRIEND_TEST(UpdateAttempterTest, RollbackAfterInstall);
   FRIEND_TEST(UpdateAttempterTest, RollbackAllowed);
   FRIEND_TEST(UpdateAttempterTest, RollbackAllowedSetAndReset);
   FRIEND_TEST(UpdateAttempterTest, RollbackMetricsNotRollbackFailure);
@@ -260,6 +268,7 @@
   FRIEND_TEST(UpdateAttempterTest, SetRollbackHappenedNotRollback);
   FRIEND_TEST(UpdateAttempterTest, SetRollbackHappenedRollback);
   FRIEND_TEST(UpdateAttempterTest, TargetVersionPrefixSetAndReset);
+  FRIEND_TEST(UpdateAttempterTest, UpdateAfterInstall);
   FRIEND_TEST(UpdateAttempterTest, UpdateAttemptFlagsCachedAtUpdateStart);
   FRIEND_TEST(UpdateAttempterTest, UpdateDeferredByPolicyTest);
   FRIEND_TEST(UpdateAttempterTest, UpdateIsNotRunningWhenUpdateAvailable);
@@ -394,11 +403,22 @@
 
   void CalculateStagingParams(bool interactive);
 
+  // Reports a metric that tracks the time from when the update was first seen
+  // to the time when the update was finally downloaded and applied. This metric
+  // will only be reported for enterprise enrolled devices.
+  void ReportTimeToUpdateAppliedMetric();
+
   // Last status notification timestamp used for throttling. Use monotonic
   // TimeTicks to ensure that notifications are sent even if the system clock is
   // set back in the middle of an update.
   base::TimeTicks last_notify_time_;
 
+  // Our two proxy resolvers
+  DirectProxyResolver direct_proxy_resolver_;
+#if USE_CHROME_NETWORK_PROXY
+  ChromeBrowserProxyResolver chrome_proxy_resolver_;
+#endif  // USE_CHROME_NETWORK_PROXY
+
   std::unique_ptr<ActionProcessor> processor_;
 
   // External state of the system outside the update_engine process
@@ -458,12 +478,6 @@
   // If true, this update cycle we are obeying proxies
   bool obeying_proxies_ = true;
 
-  // Our two proxy resolvers
-  DirectProxyResolver direct_proxy_resolver_;
-#if USE_CHROME_NETWORK_PROXY
-  ChromeBrowserProxyResolver chrome_proxy_resolver_;
-#endif  // USE_CHROME_NETWORK_PROXY
-
   // Used for fetching information about the device policy.
   std::unique_ptr<policy::PolicyProvider> policy_provider_;
 
@@ -494,6 +508,12 @@
   std::string forced_app_version_;
   std::string forced_omaha_url_;
 
+  // A list of DLC module IDs.
+  std::vector<std::string> dlc_module_ids_;
+  // Whether the operation is install (write to the current slot not the
+  // inactive slot).
+  bool is_install_;
+
   // If this is not TimeDelta(), then that means staging is turned on.
   base::TimeDelta staging_wait_time_;
   chromeos_update_manager::StagingSchedule staging_schedule_;
diff --git a/update_attempter_android.h b/update_attempter_android.h
index 0438c16..e4b40de 100644
--- a/update_attempter_android.h
+++ b/update_attempter_android.h
@@ -168,6 +168,9 @@
   // set back in the middle of an update.
   base::TimeTicks last_notify_time_;
 
+  // Only direct proxy supported.
+  DirectProxyResolver proxy_resolver_;
+
   // The processor for running Actions.
   std::unique_ptr<ActionProcessor> processor_;
 
@@ -181,9 +184,6 @@
   // The offset in the payload file where the CrAU part starts.
   int64_t base_offset_{0};
 
-  // Only direct proxy supported.
-  DirectProxyResolver proxy_resolver_;
-
   // Helper class to select the network to use during the update.
   std::unique_ptr<NetworkSelectorInterface> network_selector_;
 
diff --git a/update_attempter_unittest.cc b/update_attempter_unittest.cc
index fb9f7bc..3209f15 100644
--- a/update_attempter_unittest.cc
+++ b/update_attempter_unittest.cc
@@ -30,6 +30,7 @@
 #include <policy/mock_device_policy.h>
 #include <policy/mock_libpolicy.h>
 
+#include "update_engine/common/dlcservice_interface.h"
 #include "update_engine/common/fake_clock.h"
 #include "update_engine/common/fake_prefs.h"
 #include "update_engine/common/mock_action.h"
@@ -58,6 +59,7 @@
 using policy::DevicePolicy;
 using std::string;
 using std::unique_ptr;
+using std::vector;
 using testing::_;
 using testing::DoAll;
 using testing::Field;
@@ -77,6 +79,15 @@
 
 namespace chromeos_update_engine {
 
+namespace {
+
+class MockDlcService : public DlcServiceInterface {
+ public:
+  MOCK_METHOD1(GetInstalled, bool(vector<string>*));
+};
+
+}  // namespace
+
 const char kRollbackVersion[] = "10575.39.2";
 
 // Test a subclass rather than the main class directly so that we can mock out
@@ -89,13 +100,14 @@
 
   // Wrap the update scheduling method, allowing us to opt out of scheduled
   // updates for testing purposes.
-  void ScheduleUpdates() override {
+  bool ScheduleUpdates() override {
     schedule_updates_called_ = true;
     if (do_schedule_updates_) {
       UpdateAttempter::ScheduleUpdates();
     } else {
       LOG(INFO) << "[TEST] Update scheduling disabled.";
     }
+    return true;
   }
   void EnableScheduleUpdates() { do_schedule_updates_ = true; }
   void DisableScheduleUpdates() { do_schedule_updates_ = false; }
@@ -119,10 +131,13 @@
     // Override system state members.
     fake_system_state_.set_connection_manager(&mock_connection_manager);
     fake_system_state_.set_update_attempter(&attempter_);
+    fake_system_state_.set_dlcservice(&mock_dlcservice_);
     loop_.SetAsCurrent();
 
     certificate_checker_.Init();
 
+    attempter_.set_forced_update_pending_callback(
+        new base::Callback<void(bool, bool)>(base::Bind([](bool, bool) {})));
     // Finish initializing the attempter.
     attempter_.Init();
   }
@@ -197,6 +212,7 @@
   UpdateAttempterUnderTest attempter_{&fake_system_state_};
   OpenSSLWrapper openssl_wrapper_;
   CertificateChecker certificate_checker_;
+  MockDlcService mock_dlcservice_;
 
   NiceMock<MockActionProcessor>* processor_;
   NiceMock<MockPrefs>* prefs_;  // Shortcut to fake_system_state_->mock_prefs().
@@ -1158,6 +1174,21 @@
   EXPECT_FALSE(attempter_.IsAnyUpdateSourceAllowed());
 }
 
+TEST_F(UpdateAttempterTest, CheckForUpdateAUDlcTest) {
+  fake_system_state_.fake_hardware()->SetIsOfficialBuild(true);
+  fake_system_state_.fake_hardware()->SetAreDevFeaturesEnabled(false);
+
+  const string dlc_module_id = "a_dlc_module_id";
+  vector<string> dlc_module_ids = {dlc_module_id};
+  ON_CALL(mock_dlcservice_, GetInstalled(testing::_))
+      .WillByDefault(DoAll(testing::SetArgPointee<0>(dlc_module_ids),
+                           testing::Return(true)));
+
+  attempter_.CheckForUpdate("", "autest", UpdateAttemptFlags::kNone);
+  EXPECT_EQ(attempter_.dlc_module_ids_.size(), 1);
+  EXPECT_EQ(attempter_.dlc_module_ids_[0], dlc_module_id);
+}
+
 TEST_F(UpdateAttempterTest, CheckForUpdateAUTest) {
   fake_system_state_.fake_hardware()->SetIsOfficialBuild(true);
   fake_system_state_.fake_hardware()->SetAreDevFeaturesEnabled(false);
@@ -1172,6 +1203,42 @@
   EXPECT_EQ(constants::kOmahaDefaultAUTestURL, attempter_.forced_omaha_url());
 }
 
+TEST_F(UpdateAttempterTest, CheckForInstallTest) {
+  fake_system_state_.fake_hardware()->SetIsOfficialBuild(true);
+  fake_system_state_.fake_hardware()->SetAreDevFeaturesEnabled(false);
+  attempter_.CheckForInstall({}, "autest");
+  EXPECT_EQ(constants::kOmahaDefaultAUTestURL, attempter_.forced_omaha_url());
+
+  attempter_.CheckForInstall({}, "autest-scheduled");
+  EXPECT_EQ(constants::kOmahaDefaultAUTestURL, attempter_.forced_omaha_url());
+
+  attempter_.CheckForInstall({}, "http://omaha.phishing");
+  EXPECT_EQ("", attempter_.forced_omaha_url());
+}
+
+TEST_F(UpdateAttempterTest, InstallSetsStatusIdle) {
+  attempter_.CheckForInstall({}, "http://foo.bar");
+  attempter_.status_ = UpdateStatus::DOWNLOADING;
+  EXPECT_TRUE(attempter_.is_install_);
+  attempter_.ProcessingDone(nullptr, ErrorCode::kSuccess);
+  UpdateEngineStatus status;
+  attempter_.GetStatus(&status);
+  // Should set status to idle after an install operation.
+  EXPECT_EQ(UpdateStatus::IDLE, status.status);
+}
+
+TEST_F(UpdateAttempterTest, RollbackAfterInstall) {
+  attempter_.is_install_ = true;
+  attempter_.Rollback(false);
+  EXPECT_FALSE(attempter_.is_install_);
+}
+
+TEST_F(UpdateAttempterTest, UpdateAfterInstall) {
+  attempter_.is_install_ = true;
+  attempter_.CheckForUpdate("", "", UpdateAttemptFlags::kNone);
+  EXPECT_FALSE(attempter_.is_install_);
+}
+
 TEST_F(UpdateAttempterTest, TargetVersionPrefixSetAndReset) {
   attempter_.CalculateUpdateParams("", "", "", "1234", false, false, false);
   EXPECT_EQ("1234",
@@ -1421,4 +1488,84 @@
   attempter_.ProcessingDone(nullptr, ErrorCode::kRollbackNotPossible);
 }
 
+TEST_F(UpdateAttempterTest, TimeToUpdateAppliedMetricFailure) {
+  EXPECT_CALL(*fake_system_state_.mock_metrics_reporter(),
+              ReportEnterpriseUpdateSeenToDownloadDays(_, _))
+      .Times(0);
+  attempter_.ProcessingDone(nullptr, ErrorCode::kOmahaUpdateDeferredPerPolicy);
+}
+
+TEST_F(UpdateAttempterTest, TimeToUpdateAppliedOnNonEnterprise) {
+  auto device_policy = std::make_unique<policy::MockDevicePolicy>();
+  fake_system_state_.set_device_policy(device_policy.get());
+  // Make device policy return that this is not enterprise enrolled
+  EXPECT_CALL(*device_policy, IsEnterpriseEnrolled()).WillOnce(Return(false));
+
+  // Ensure that the metric is not recorded.
+  EXPECT_CALL(*fake_system_state_.mock_metrics_reporter(),
+              ReportEnterpriseUpdateSeenToDownloadDays(_, _))
+      .Times(0);
+  attempter_.ProcessingDone(nullptr, ErrorCode::kSuccess);
+}
+
+TEST_F(UpdateAttempterTest,
+       TimeToUpdateAppliedWithTimeRestrictionMetricSuccess) {
+  constexpr int kDaysToUpdate = 15;
+  auto device_policy = std::make_unique<policy::MockDevicePolicy>();
+  fake_system_state_.set_device_policy(device_policy.get());
+  // Make device policy return that this is enterprise enrolled
+  EXPECT_CALL(*device_policy, IsEnterpriseEnrolled()).WillOnce(Return(true));
+  // Pretend that there's a time restriction policy in place
+  EXPECT_CALL(*device_policy, GetDisallowedTimeIntervals(_))
+      .WillOnce(Return(true));
+
+  FakePrefs fake_prefs;
+  Time update_first_seen_at = Time::Now();
+  fake_prefs.SetInt64(kPrefsUpdateFirstSeenAt,
+                      update_first_seen_at.ToInternalValue());
+
+  FakeClock fake_clock;
+  Time update_finished_at =
+      update_first_seen_at + TimeDelta::FromDays(kDaysToUpdate);
+  fake_clock.SetWallclockTime(update_finished_at);
+
+  fake_system_state_.set_clock(&fake_clock);
+  fake_system_state_.set_prefs(&fake_prefs);
+
+  EXPECT_CALL(*fake_system_state_.mock_metrics_reporter(),
+              ReportEnterpriseUpdateSeenToDownloadDays(true, kDaysToUpdate))
+      .Times(1);
+  attempter_.ProcessingDone(nullptr, ErrorCode::kSuccess);
+}
+
+TEST_F(UpdateAttempterTest,
+       TimeToUpdateAppliedWithoutTimeRestrictionMetricSuccess) {
+  constexpr int kDaysToUpdate = 15;
+  auto device_policy = std::make_unique<policy::MockDevicePolicy>();
+  fake_system_state_.set_device_policy(device_policy.get());
+  // Make device policy return that this is enterprise enrolled
+  EXPECT_CALL(*device_policy, IsEnterpriseEnrolled()).WillOnce(Return(true));
+  // Pretend that there's no time restriction policy in place
+  EXPECT_CALL(*device_policy, GetDisallowedTimeIntervals(_))
+      .WillOnce(Return(false));
+
+  FakePrefs fake_prefs;
+  Time update_first_seen_at = Time::Now();
+  fake_prefs.SetInt64(kPrefsUpdateFirstSeenAt,
+                      update_first_seen_at.ToInternalValue());
+
+  FakeClock fake_clock;
+  Time update_finished_at =
+      update_first_seen_at + TimeDelta::FromDays(kDaysToUpdate);
+  fake_clock.SetWallclockTime(update_finished_at);
+
+  fake_system_state_.set_clock(&fake_clock);
+  fake_system_state_.set_prefs(&fake_prefs);
+
+  EXPECT_CALL(*fake_system_state_.mock_metrics_reporter(),
+              ReportEnterpriseUpdateSeenToDownloadDays(false, kDaysToUpdate))
+      .Times(1);
+  attempter_.ProcessingDone(nullptr, ErrorCode::kSuccess);
+}
+
 }  // namespace chromeos_update_engine
diff --git a/update_engine.gyp b/update_engine.gyp
index 358f64a..2d81bed 100644
--- a/update_engine.gyp
+++ b/update_engine.gyp
@@ -13,6 +13,9 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
+
+# TODO: Rename these files to pass this check.
+# gyplint: disable=GypLintSourceFileNames
 {
   'variables': {
     'USE_chrome_network_proxy': '1',
@@ -23,6 +26,11 @@
       'deps': [
         'libbrillo-<(libbase_ver)',
         'libchrome-<(libbase_ver)',
+        # system_api depends on protobuf (or protobuf-lite). It must appear
+        # before protobuf here or the linker flags won't be in the right
+        # order.
+        'system_api',
+        'protobuf-lite',
       ],
       # The -DUSE_* flags are passed from platform2.py. We use sane defaults
       # here when these USE flags are not defined. You can set the default value
@@ -73,17 +81,6 @@
       'variables': {
         'proto_in_dir': '.',
         'proto_out_dir': 'include/update_engine',
-        'exported_deps': [
-          'protobuf-lite',
-        ],
-        'deps': ['<@(exported_deps)'],
-      },
-      'all_dependent_settings': {
-        'variables': {
-          'deps': [
-            '<@(exported_deps)',
-          ],
-        },
       },
       'sources': [
         'update_metadata.proto',
@@ -131,6 +128,7 @@
         'exported_deps': [
           'libcrypto',
           'xz-embedded',
+          'libbspatch',
           'libpuffpatch',
         ],
         'deps': ['<@(exported_deps)'],
@@ -149,7 +147,6 @@
           ],
         },
         'libraries': [
-          '-lbspatch',
           '-lbz2',
           '-lrt',
         ],
@@ -228,6 +225,13 @@
           'libupdate_engine-client',
           'vboot_host',
         ],
+        'conditions':[
+          ['USE_dlc == 1', {
+            'exported_deps' : [
+              'libdlcservice-client',
+            ],
+          }],
+        ],
         'deps': ['<@(exported_deps)'],
       },
       'all_dependent_settings': {
@@ -312,6 +316,16 @@
             'update_engine-dbus-kiosk-app-client',
           ],
         }],
+        ['USE_dlc == 1', {
+          'sources': [
+            'dlcservice_chromeos.cc',
+          ],
+        }],
+        ['USE_dlc == 0', {
+          'sources': [
+            'common/dlcservice_stub.cc',
+          ],
+        }],
       ],
     },
     # update_engine daemon.
@@ -369,6 +383,7 @@
       'variables': {
         'exported_deps': [
           'ext2fs',
+          'libbsdiff',
           'libpuffdiff',
           'liblzma',
         ],
@@ -387,9 +402,6 @@
             '<@(exported_deps)',
           ],
         },
-        'libraries': [
-          '-lbsdiff',
-        ],
       },
       'sources': [
         'payload_generator/ab_generator.cc',
@@ -438,6 +450,32 @@
         'payload_generator/generate_delta_main.cc',
       ],
     },
+    {
+      'target_name': 'update_engine_test_libs',
+      'type': 'static_library',
+      'variables': {
+        'deps': [
+          'libshill-client-test',
+        ],
+      },
+      'dependencies': [
+        'libupdate_engine',
+      ],
+      'includes': [
+        '../../../platform2/common-mk/common_test.gypi',
+      ],
+      'sources': [
+        'common/fake_prefs.cc',
+        'common/file_fetcher.cc',  # Only required for tests.
+        'common/mock_http_fetcher.cc',
+        'common/test_utils.cc',
+        'fake_shill_proxy.cc',
+        'fake_system_state.cc',
+        'payload_consumer/fake_file_descriptor.cc',
+        'payload_generator/fake_filesystem.cc',
+        'update_manager/umtest_utils.cc',
+      ],
+    },
   ],
   'conditions': [
     ['USE_test == 1', {
@@ -502,8 +540,8 @@
           'dependencies': [
             'libupdate_engine',
             'libpayload_generator',
+            'update_engine_test_libs',
           ],
-          'includes': ['../../../platform2/common-mk/common_test.gypi'],
           'sources': [
             'boot_control_chromeos_unittest.cc',
             'certificate_checker_unittest.cc',
@@ -511,22 +549,16 @@
             'common/action_processor_unittest.cc',
             'common/action_unittest.cc',
             'common/cpu_limiter_unittest.cc',
-            'common/fake_prefs.cc',
-            'common/file_fetcher.cc',  # Only required for tests.
             'common/hash_calculator_unittest.cc',
             'common/http_fetcher_unittest.cc',
             'common/hwid_override_unittest.cc',
-            'common/mock_http_fetcher.cc',
             'common/prefs_unittest.cc',
             'common/proxy_resolver_unittest.cc',
             'common/subprocess_unittest.cc',
             'common/terminator_unittest.cc',
-            'common/test_utils.cc',
             'common/utils_unittest.cc',
             'common_service_unittest.cc',
             'connection_manager_unittest.cc',
-            'fake_shill_proxy.cc',
-            'fake_system_state.cc',
             'hardware_chromeos_unittest.cc',
             'image_properties_chromeos_unittest.cc',
             'metrics_reporter_omaha_unittest.cc',
@@ -543,7 +575,6 @@
             'payload_consumer/download_action_unittest.cc',
             'payload_consumer/extent_reader_unittest.cc',
             'payload_consumer/extent_writer_unittest.cc',
-            'payload_consumer/fake_file_descriptor.cc',
             'payload_consumer/file_descriptor_utils_unittest.cc',
             'payload_consumer/file_writer_unittest.cc',
             'payload_consumer/filesystem_verifier_action_unittest.cc',
@@ -559,7 +590,6 @@
             'payload_generator/ext2_filesystem_unittest.cc',
             'payload_generator/extent_ranges_unittest.cc',
             'payload_generator/extent_utils_unittest.cc',
-            'payload_generator/fake_filesystem.cc',
             'payload_generator/full_update_generator_unittest.cc',
             'payload_generator/graph_utils_unittest.cc',
             'payload_generator/inplace_generator_unittest.cc',
@@ -587,7 +617,6 @@
             'update_manager/real_time_provider_unittest.cc',
             'update_manager/real_updater_provider_unittest.cc',
             'update_manager/staging_utils_unittest.cc',
-            'update_manager/umtest_utils.cc',
             'update_manager/update_manager_unittest.cc',
             'update_manager/update_time_restrictions_policy_impl_unittest.cc',
             'update_manager/variable_unittest.cc',
@@ -596,5 +625,30 @@
         },
       ],
     }],
+    # Fuzzer target.
+    ['USE_fuzzer == 1', {
+      'targets': [
+        {
+          'target_name': 'update_engine_omaha_request_action_fuzzer',
+          'type': 'executable',
+          'variables': {
+            'deps': [
+              'libbrillo-test-<(libbase_ver)',
+              'libchrome-test-<(libbase_ver)',
+            ],
+          },
+          'includes': [
+            '../../../platform2/common-mk/common_fuzzer.gypi',
+          ],
+          'dependencies': [
+            'libupdate_engine',
+            'update_engine_test_libs',
+          ],
+          'sources': [
+            'omaha_request_action_fuzzer.cc',
+          ],
+        },
+      ],
+    }],
   ],
 }
diff --git a/update_engine_client.cc b/update_engine_client.cc
index b7096c5..f36949b 100644
--- a/update_engine_client.cc
+++ b/update_engine_client.cc
@@ -26,6 +26,7 @@
 #include <base/command_line.h>
 #include <base/logging.h>
 #include <base/macros.h>
+#include <base/strings/string_split.h>
 #include <base/threading/platform_thread.h>
 #include <brillo/daemons/daemon.h>
 #include <brillo/flag_helper.h>
@@ -294,6 +295,8 @@
               "Show the previous OS version used before the update reboot.");
   DEFINE_bool(last_attempt_error, false, "Show the last attempt error.");
   DEFINE_bool(eol_status, false, "Show the current end-of-life status.");
+  DEFINE_bool(install, false, "Requests an install.");
+  DEFINE_string(dlc_module_ids, "", "colon-separated list of DLC IDs.");
 
   // Boilerplate init commands.
   base::CommandLine::Init(argc_, argv_);
@@ -477,6 +480,30 @@
     }
   }
 
+  if (FLAGS_install) {
+    // Parse DLC module IDs.
+    vector<string> dlc_module_ids;
+    if (!FLAGS_dlc_module_ids.empty()) {
+      dlc_module_ids = base::SplitString(FLAGS_dlc_module_ids,
+                                         ":",
+                                         base::TRIM_WHITESPACE,
+                                         base::SPLIT_WANT_ALL);
+    }
+    if (dlc_module_ids.empty()) {
+      LOG(ERROR) << "dlc_module_ids is empty:" << FLAGS_dlc_module_ids;
+      return 1;
+    }
+    if (!client_->AttemptInstall(FLAGS_omaha_url, dlc_module_ids)) {
+      LOG(ERROR) << "AttemptInstall failed.";
+      return 1;
+    }
+    return 0;
+  } else if (!FLAGS_dlc_module_ids.empty()) {
+    LOG(ERROR) << "dlc_module_ids is not empty while install is not set:"
+               << FLAGS_dlc_module_ids;
+    return 1;
+  }
+
   // Initiate an update check, if necessary.
   if (do_update_request) {
     LOG_IF(WARNING, FLAGS_reboot) << "-reboot flag ignored.";
diff --git a/update_manager/boxed_value.cc b/update_manager/boxed_value.cc
index 8ec3375..ffbd00b 100644
--- a/update_manager/boxed_value.cc
+++ b/update_manager/boxed_value.cc
@@ -144,8 +144,12 @@
       return "Unspecified";
     case RollbackToTargetVersion::kDisabled:
       return "Disabled";
-    case RollbackToTargetVersion::kRollbackWithFullPowerwash:
-      return "Rollback with full powerwash";
+    case RollbackToTargetVersion::kRollbackAndPowerwash:
+      return "Rollback and powerwash";
+    case RollbackToTargetVersion::kRollbackAndRestoreIfPossible:
+      return "Rollback and restore if possible";
+    case RollbackToTargetVersion::kRollbackOnlyIfRestorePossible:
+      return "Rollback only if restore is possible";
     case RollbackToTargetVersion::kMaxValue:
       NOTREACHED();
       return "Max value";
diff --git a/update_manager/boxed_value_unittest.cc b/update_manager/boxed_value_unittest.cc
index 3fa0f1a..212db36 100644
--- a/update_manager/boxed_value_unittest.cc
+++ b/update_manager/boxed_value_unittest.cc
@@ -161,6 +161,9 @@
 }
 
 TEST(UmBoxedValueTest, ConnectionTypeToString) {
+  EXPECT_EQ(
+      "Disconnected",
+      BoxedValue(new ConnectionType(ConnectionType::kDisconnected)).ToString());
   EXPECT_EQ("ethernet",
             BoxedValue(new ConnectionType(ConnectionType::kEthernet))
             .ToString());
@@ -203,10 +206,20 @@
             BoxedValue(
                 new RollbackToTargetVersion(RollbackToTargetVersion::kDisabled))
                 .ToString());
-  EXPECT_EQ("Rollback with full powerwash",
+  EXPECT_EQ("Rollback and powerwash",
             BoxedValue(new RollbackToTargetVersion(
-                           RollbackToTargetVersion::kRollbackWithFullPowerwash))
+                           RollbackToTargetVersion::kRollbackAndPowerwash))
                 .ToString());
+  EXPECT_EQ(
+      "Rollback and restore if possible",
+      BoxedValue(new RollbackToTargetVersion(
+                     RollbackToTargetVersion::kRollbackAndRestoreIfPossible))
+          .ToString());
+  EXPECT_EQ(
+      "Rollback only if restore is possible",
+      BoxedValue(new RollbackToTargetVersion(
+                     RollbackToTargetVersion::kRollbackOnlyIfRestorePossible))
+          .ToString());
 }
 
 TEST(UmBoxedValueTest, SetConnectionTypeToString) {
diff --git a/update_manager/chromeos_policy_unittest.cc b/update_manager/chromeos_policy_unittest.cc
index 96f3d79..15bb09f 100644
--- a/update_manager/chromeos_policy_unittest.cc
+++ b/update_manager/chromeos_policy_unittest.cc
@@ -273,9 +273,21 @@
   EXPECT_FALSE(result.interactive);
 }
 
-TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedRollbackAllowed) {
+TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedRollbackAndPowerwash) {
   EXPECT_TRUE(TestRollbackAllowed(
-      true, RollbackToTargetVersion::kRollbackWithFullPowerwash));
+      true, RollbackToTargetVersion::kRollbackAndPowerwash));
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedRollbackAndRestoreIfPossible) {
+  // We're doing rollback even if we don't support data save and restore.
+  EXPECT_TRUE(TestRollbackAllowed(
+      true, RollbackToTargetVersion::kRollbackAndRestoreIfPossible));
+}
+
+TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedRollbackOnlyIfRestorePossible) {
+  // We're not allowed to do rollback until we support data save and restore.
+  EXPECT_FALSE(TestRollbackAllowed(
+      true, RollbackToTargetVersion::kRollbackOnlyIfRestorePossible));
 }
 
 TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedRollbackDisabled) {
@@ -296,7 +308,7 @@
   SetKioskAppControlsChromeOsVersion();
 
   EXPECT_TRUE(TestRollbackAllowed(
-      true, RollbackToTargetVersion::kRollbackWithFullPowerwash));
+      true, RollbackToTargetVersion::kRollbackAndPowerwash));
 }
 
 TEST_F(UmChromeOSPolicyTest, UpdateCheckAllowedKioskRollbackDisabled) {
diff --git a/update_manager/enterprise_device_policy_impl.cc b/update_manager/enterprise_device_policy_impl.cc
index 6f14b1f..a3430ef 100644
--- a/update_manager/enterprise_device_policy_impl.cc
+++ b/update_manager/enterprise_device_policy_impl.cc
@@ -92,10 +92,22 @@
           LOG(INFO) << "Policy disables rollbacks.";
           result->rollback_allowed = false;
           break;
-        case RollbackToTargetVersion::kRollbackWithFullPowerwash:
-          LOG(INFO) << "Policy allows rollbacks.";
+        case RollbackToTargetVersion::kRollbackAndPowerwash:
+          LOG(INFO) << "Policy allows rollbacks with powerwash.";
           result->rollback_allowed = true;
           break;
+        case RollbackToTargetVersion::kRollbackAndRestoreIfPossible:
+          LOG(INFO)
+              << "Policy allows rollbacks, also tries to restore if possible.";
+          // We don't support restore yet, but policy still allows rollback.
+          result->rollback_allowed = true;
+          break;
+        case RollbackToTargetVersion::kRollbackOnlyIfRestorePossible:
+          LOG(INFO) << "Policy only allows rollbacks if restore is possible.";
+          // We don't support restore yet, policy doesn't allow rollback in this
+          // case.
+          result->rollback_allowed = false;
+          break;
         case RollbackToTargetVersion::kMaxValue:
           NOTREACHED();
           // Don't add a default case to let the compiler warn about newly
diff --git a/update_manager/real_device_policy_provider_unittest.cc b/update_manager/real_device_policy_provider_unittest.cc
index 32e273d..e9c7b25 100644
--- a/update_manager/real_device_policy_provider_unittest.cc
+++ b/update_manager/real_device_policy_provider_unittest.cc
@@ -245,7 +245,7 @@
   loop_.RunOnce(false);
 
   UmTestUtils::ExpectVariableHasValue(
-      RollbackToTargetVersion::kRollbackWithFullPowerwash,
+      RollbackToTargetVersion::kRollbackAndPowerwash,
       provider_->var_rollback_to_target_version());
 }
 
diff --git a/update_manager/rollback_prefs.h b/update_manager/rollback_prefs.h
index 1783eb0..11d09d6 100644
--- a/update_manager/rollback_prefs.h
+++ b/update_manager/rollback_prefs.h
@@ -29,9 +29,11 @@
 enum class RollbackToTargetVersion {
   kUnspecified = 0,
   kDisabled = 1,
-  kRollbackWithFullPowerwash = 2,
+  kRollbackAndPowerwash = 2,
+  kRollbackAndRestoreIfPossible = 3,
+  kRollbackOnlyIfRestorePossible = 4,
   // This value must be the last entry.
-  kMaxValue = 3
+  kMaxValue = 5
 };
 
 }  // namespace chromeos_update_manager